Linux 网络编程(五)——如何优雅的断开套接字连接

文章目录

5 如何优雅地断开套接字连接

5.1 基于TCP的半关闭

5.1.1 套接字和流(Stream)

5.1.2 针对优雅断开的shutdown函数

5.1.3 为何需要半关闭

5.1.4 基于半关闭的文件传输程序


5 如何优雅地断开套接字连接

5.1 基于TCP的半关闭

Linux 的 close 函数意味着完全断开连接。完全断开不仅指无法传输数据,而且也不能接收数据。因此在某些情况下,通信一方调用 close 函数,显得不太优雅。例如,主机 A 发送完最后的数据后,调用 close 函数断开最后的连接,之后主机 A 无法再接受主机 B 传输的数据。最终,由主机 B 传输的、主机 A 必须要接受的数据也销毁了。

为了解决这类问题,“只关闭一部分数据交换中使用的流的方法应运而生”。断开一部分连接是指,可以传输数据但是无法接收,或可以接受数据但无法传输。

5.1.1 套接字和流(Stream)

两台主机通过套接字建立连接后进入可交换数据的状态,又称流形成的状态

一旦两台主机之间建立了套接字连接,每个主机就会拥有单独的输入流和输出流。其中一个主机的输入流与另一个主机的输出流相连,而输出流则与另一个主机的输入流相连。断开连接方式只断开其中 1 个流,而非同时断开两个流。Linux 的 close 函数和 Windows 的 closesocket 函数将同时断开这两个流,这样的方式确实不够优雅。

5.1.2 针对优雅断开的shutdown函数

在 Socket 编程中,shutdown 函数的作用已在相关章节中进行了说明,但之前的示例中均使用close 来断开连接。为了更清晰地理解其用途,这里再次给出shutdown函数的定义。

int shutdown(int sockfd,int how);
/**
* 关闭socket的一部分或全部连接
* sockfd:Socket描述符。
* how:指定关闭的类型,可以取值为:
* 	SHUT_RD:断开输入流,无法接收数据,即使输入缓冲收到数据也会抹去,而且无法调用相关函数
* 	SHUT_WR:断开输出流,无法传输数据。如果输出缓冲中还有未传输的数据,则将传递给目标主机
*   SHUT_RDWR:同时输入流和输出流,当于分 2 次调用 shutdown ,其中一次以SHUT_RD为参数,
*              另一次以 SHUT_WR为参数
* return:成功返回0,失败返回-1,同时设置errno
*/

5.1.3 为何需要半关闭

(1)为什么需要半关闭?是否只要留出足够长的时间,保证完成数据交换即可?只要不急于断开连接,好像也没必要使用半关闭?(客户端断开连接前还有数据要传输的情况)

我们假设这样一个场景:“一旦客户端连接到服务器端,服务器端将约定的文件传给客户端,客户端收到后发送字符串 “Thankyou' 给服务器端。”

传输文件的服务器端只需连续传输文件数据即可,而客户端无法知道需要接收数据到何时。客户端也没办法无休止的调用输入函数,因为这有可能导致程序阻塞。

(2)是否可以让服务器和客户端约定一个代表文件尾的字符?

这种方式也有问题,因为这意味这文件中不能有与约定字符相同的内容。为了解决该问题,服务端应最后向客户端传递 EOF 表示文件传输结束,客户端通过函数返回值接收 EOF ,这样可以避免与文件内容冲突。

(3)服务器何时传递EOF?

断开输出流时向主机传输 EOF。调用 close 函数的会关闭 I/O 流,这样也会向对方发送 EOF ,但此时无法再接受对方传输的数据。换言之,若调用 close 函数关闭流,就无法接受客户端最后发送的字符串(Thank you)。这时需要调用 shutdown 函数,只关闭服务器的输出流。这样既可以发送 EOF ,同时又保留了输入流。下面实现收发文件的服务器端/客户端。

5.1.4 基于半关闭的文件传输程序

上述文件传输服务器端和客户端的数据流可以整理如图

(1)服务器端 file_server.c

#define BUF_SIZE 1024
#define handle_error(cmd,result)    \
    if(result < 0)                  \
    {                               \
        perror(cmd);                \
        return -1;                  \
    }                               \

int main(int argc, char const *argv[])
{
    struct sockaddr_in serv_addr,clnt_addr;
    char* buf = malloc(sizeof(char)*BUF_SIZE);
    memset(&serv_addr,0,sizeof(serv_addr));
    memset(&clnt_addr,0,sizeof(clnt_addr));
    if(argc != 2) {
        printf("Usage:%s <port>\n",argv[0]);
        exit(1);
    }
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(atoi(argv[1]));
    inet_pton(AF_INET,"0.0.0.0",&serv_addr.sin_addr);
    int serv_sock = socket(AF_INET,SOCK_STREAM,0);
    handle_error("socket",serv_sock);
    int temp=bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    handle_error("bind",temp);
    temp= listen(serv_sock,128);
    handle_error("listen",temp);
    socklen_t clnt_len = sizeof(clnt_addr);
    int clnt_sock = accept(serv_sock,(struct sockaddr *)&clnt_addr,&clnt_len);
    handle_error("accept",clnt_sock);

    int fd = open("file_server.c",O_RDWR);
    if(fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    while (1)
    {
        //将file_server.c里的数据读入缓冲区buf
        int read_cnt = read(fd,buf,BUF_SIZE);
        //读到文件末尾
        if(read_cnt < BUF_SIZE) {
            send(clnt_sock,buf,read_cnt,0);
            break;
        }
        send(clnt_sock,buf,BUF_SIZE,0);
    }
    //关闭写端
    shutdown(clnt_sock,SHUT_WR);
    recv(clnt_sock,buf,BUF_SIZE,0);
    printf("Message from client:%s\n",buf);
    close(fd);
    close(serv_sock);
    free(buf);
    return 0;
}

(2)客户端 file_clint.c

#define BUF_SIZE 1024
#define handle_error(cmd,result)    \
    if(result < 0)                  \
    {                               \
        perror(cmd);                \
        return -1;                  \
    }                               \

int main(int argc, char const *argv[])
{
    // FILE* file;
    struct sockaddr_in serv_addr,clnt_addr;
    char* buf = malloc(sizeof(char)*BUF_SIZE);
    char message[] = "Thank you";
    memset(&serv_addr,0,sizeof(serv_addr));
    memset(&clnt_addr,0,sizeof(clnt_addr));
    if(argc != 3) {
        printf("Usage:%s <IP> <port>\n",argv[0]);
        exit(1);
    }
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(atoi(argv[2]));
    inet_pton(AF_INET,argv[1],&serv_addr.sin_addr);
    int clnt_sock = socket(AF_INET,SOCK_STREAM,0);
    handle_error("socket",clnt_sock);
    int temp=connect(clnt_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    handle_error("connect",temp);
    printf("Connected......\n");

    int read_cnt;
    int fd = open("received_file.dat", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    handle_error("open", fd);
    while ((read_cnt = recv(clnt_sock,buf,BUF_SIZE,0)) != 0) {
        write(fd,buf,read_cnt);
    }
    printf("Received file data\n");
    send(clnt_sock,message,sizeof(message),0);
    close(fd);
    close(clnt_sock);
    free(buf);
    return 0;
}

(3)测试结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值