• 网络编程:优雅的关闭


    参考:盛延敏:网络编程实战

    一、close函数

    close函数:

    int close(int sockfd)
    

    对已连接的套接字执行 close 操作就可以,若成功则为 0,若出错则为 -1。这个函数会对套接字引用计数减一,一旦发现套接字引用计数到 0,就会对套接字进行彻底释放,并且会关闭 TCP 两个方向的数据流。

    套接字引用计数是什么意思呢?因为套接字可以被多个进程共享

    • 在输入方向,系统内核会将该套接字设置为不可读,任何读操作都会返回异常。
    • 在输出方向,系统内核尝试将发送缓冲区的数据发送给对端,并最后向对端发送一个 FIN 报文,接下来如果再对该套接字进行写操作会返回异常。
    • 如果对端没有检测到套接字已关闭,还继续发送报文,就会收到一个 RST 报文,告诉对端:“Hi, 我已经关闭了,别再给我发数据了。”

    close函数并不能帮助关闭连接的一个方向。

    二、shutdown函数

    shutdown 函数的原型

    int shutdown(int sockfd, int howto)
    

    若成功则为 0,若出错则为 -1
    howto参数有三种情况:

    • SHUT_RD(0):关闭连接的“读”这个方向,对该套接字进行读操作直接返回 EOF。从数据角度来看,套接字上接收缓冲区已有的数据将被丢弃,如果再有新的数据流到达,会对数据进行 ACK,然后悄悄地丢弃。也就是说,对端还是会接收到 ACK,在这种情况下根本不知道数据已经被丢弃了。
    • SHUT_WR(1):关闭连接的“写”这个方向,这就是常被称为“半关闭”的连接。此时,不管套接字引用计数的值是多少,都会直接关闭连接的写方向。套接字上发送缓冲区已有的数据将被立即发送出去,并发送一个 FIN 报文给对端。应用程序如果对该套接字进行写操作会报错。
    • SHUT_RDWR(2):相当于 SHUT_RD 和 SHUT_WR 操作各一次,关闭套接字的读和写两个方向。

    close函数和shutdown函数的差别

    第一个差别:close 会关闭连接,并释放所有连接对应的资源,而 shutdown 并不会释放掉套接字和所有的资源。
    第二个差别:close 存在引用计数的概念,并不一定导致该套接字不可用;shutdown 则不管引用计数,直接使得该套接字不可用,如果有别的进程企图使用该套接字,将会受到影响。
    第三个差别:close 的引用计数导致不一定会发出 FIN 结束报文,而 shutdown 则总是会发出 FIN 结束报文,这在我们打算关闭连接通知对端的时候,是非常重要的。

    程序体验差别

    服务端

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <signal.h>
    
    #define MAXLINE 4096
    #define SERV_PORT 43211
    #define LISTENQ 1024
    
    
    static int count;
    static void sig_int(int signo)
    {
        printf("\nreceived %d datagrams\n", count);
        exit(0);
    }
    
    
    int main(int argc, char *argv[])
    {
        int listenfd;
    
        listenfd = socket(AF_INET, SOCK_STREAM, 0);
        
        struct sockaddr_in server_addr;
        bzero(&server_addr, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(SERV_PORT);
        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        int rt1 = bind(listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
        if( rt1 < 0)
        {
            perror("bind failed");
            return -1;
        }
    
        int rt2 = listen(listenfd, LISTENQ);
        if(rt2 < 0)
        {
            perror("listen failed");
            return -1;
        }
    
        signal(SIGINT, sig_int);
        signal(SIGPIPE, SIG_DFL);
    
        int connfd;
        struct sockaddr_in client_addr;
        socklen_t client_len = sizeof(client_addr);
    
        if((connfd = accept(listenfd, (struct sockaddr *)&client_addr, &client_len)) < 0)
        {
            perror("accept failed");
            return -1;
        }
    
        char message[MAXLINE];
        count = 0;
    
        for(;;)
        {
            int n = read(connfd, message, MAXLINE);
            if(n < 0)
            {
                perror("error read");
                return -1;
            }
            else if(n == 0)
            {
                perror("client closed ");
                return -1;
            }
    
            message[n] = 0;
            printf("received %d bytes: %s\n", n, message);
            count++;
    
            char send_line[MAXLINE];
            sprintf(send_line, "Hi, %s",message);
    
            sleep(5);
    
            int write_nc = send(connfd, send_line, strlen(send_line), 0);
            printf("send bytes: %zu \n",write_nc);
            if(write_nc < 0)
            {
                perror("error write");
            }
        }
    
    
    }
    

    服务端:

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/select.h>
    
    #define MAXLINE 4096
    #define SERV_PORT 43211
    
    
    int main(int argc, char *argv[])
    {
        if(argc != 2)
        {
            perror("usage:graceserver <IPaddress>");
            return -1;
        }
    
        int socket_fd;
        socket_fd = socket(AF_INET, SOCK_STREAM, 0);
        
        struct sockaddr_in server_addr;
        bzero(&server_addr, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(SERV_PORT);
        inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
    
        socklen_t server_len = sizeof(server_addr);
        int connect_rt = connect(socket_fd, (struct sockaddr *)&server_addr, server_len);
        if(connect_rt < 0)
        {
            perror("connect failed");
            return -1;
        }
    
        char send_line[MAXLINE],recv_line[MAXLINE+1];
        int n;
    
        fd_set readmask; 
        fd_set allreads;
    
        FD_ZERO(&allreads);
        FD_SET(0, &allreads);
        FD_SET(socket_fd, &allreads);
        for(;;)
        {
            readmask = allreads;
            int rc = select(socket_fd+1, &readmask, NULL, NULL, NULL);
            if(rc <= 0)
            {
                perror("select failed");
                return -1;
            }
            if(FD_ISSET(socket_fd, &readmask))
            {
                n = read(socket_fd, recv_line, MAXLINE);
                if(n < 0)
                {
                    perror("read error");
                    return -1;
                }
                else if(n == 0)
                {
                    perror("server terminated\n");
                    return -1;
                }
    
                printf("==========");
                recv_line[n] = 0;
                fputs(recv_line, stdout);
                fputs("\n",stdout);
            }
            if(FD_ISSET(0, &readmask))
            {
                if(fgets(send_line, MAXLINE, stdin) != NULL)
                {
                    if(strncmp(send_line, "shutdown", 8) == 0)
                    {
                        FD_CLR(0, &allreads);
                        if(shutdown(socket_fd, 1))
                        {
                            perror("shudown failed");
                            return -1;
                        }
                    }
                    else if(strncmp(send_line, "close", 5) == 0)
                    {
                        FD_CLR(0, &allreads);
                        if(close(socket_fd))
                        {
                            perror("close failed");
                            return -1;
                        }
                        sleep(6);
                        exit(0);
                    }
                    else
                    {
                        int i = strlen(send_line);
                        if(send_line[i - 1] == '\n')
                        {
                            send_line[i - 1] = 0;
                        }
    
                        printf("now sending %s\n",send_line);
                        size_t rt = write(socket_fd, send_line, strlen(send_line));
                        if(rt < 0)
                        {
                            perror("write failed..");
                            return -1;
                        }
                        printf("send bytes: %zu \n",rt);
                    }
                }
            }
    
        }
    
    }
    

    运行效果:
    close:

    在客户端 close 掉整个连接之后,服务器端接收到 SIGPIPE 信号,直接退出。客户端并没有收到服务器端的应答数据。
    shutdown:

    服务器端输出了 data1、data2;客户端也输出了“Hi,data1”和“Hi,data2”,客户端和服务器端各自完成了自己的工作后,正常退出

    两者的时序图如下:

    客户端调用 shutdown 函数只是关闭连接的一个方向,服务器端到客户端的这个方向还可以继续进行数据的发送和接收,所以“Hi,data1”和“Hi,data2”都可以正常传送;当服务器端读到 EOF 时,立即向客户端发送了 FIN 报文,客户端在 read 函数中感知了 EOF,也进行了正常退出。

    小结

    close 函数只是把套接字引用计数减 1,未必会立即关闭连接;
    close 函数如果在套接字引用计数达到 0 时,立即终止读和写两个方向的数据传送。
    基于这两点,在期望关闭连接其中一个方向时,应该使用 shutdown 函数。

  • 相关阅读:
    JVM学习笔记(三)------内存管理和垃圾回收【转】
    JVM学习笔记(二)------Java代码编译和执行的整个过程【转】
    JVM学习笔记(一)------基本结构【转】
    Java程序编译和运行的过程【转】
    linux C判断文件是否存在【转】
    Java编译那些事儿【转】
    CTSC1999补丁VS错误题解
    ASP.NET MVC学前篇之Ninject的初步了解
    setSingleChoiceItems和setPositiveButton两者触发时期
    B. Sereja and Mirroring
  • 原文地址:https://www.cnblogs.com/whiteBear/p/15983228.html
Copyright © 2020-2023  润新知