调用close()后发生了什么

场景介绍

咱们考虑简单的客户端——服务器通讯的场景,其典型模式为:linux

  • 服务端经过close()主动关闭一个TCP链接编程

  • 客户端经过read()得到了0(表示服务端没有数据),调用close()关闭这个链接。服务器

在TCP层面表现为:网络

  • 服务器调用close()后,向客户端发送FIN,客户端回应FIN-ACK。服务器进入FIN-WAIT-2状态,客户端进入CLOSE-WAIT状态。socket

  • 客户端调用close()后,向服务端发送FIN,服务端会用FIN-ACK。服务端进入TIME-WAIT状态,客户端直接进入CLOSE状态,链接结束。tcp

咱们考虑一些异常状况,客户端上:测试

  • 客户端在得到read()==0后,没有及时地调用close()调用;ui

  • 客户端在得到read()==0后,仍然向服务端写入数据。code

测试程序

选用《UNIX网络编程》(第一卷)中的服务器——客户端样例程序。服务器部分:server

int main(int argc, char **argv)
    {
        int                    listenfd, connfd;
        socklen_t            len;
        struct sockaddr_in    servaddr, cliaddr;
        char                buff[MAXLINE];
        time_t                ticks;
    
        listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family      = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port        = htons(9999);    /* daytime server */
    
        Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
    
        Listen(listenfd, LISTENQ);
    
        for ( ; ; ) {
            len = sizeof(cliaddr);
            connfd = Accept(listenfd, (SA *) &cliaddr, &len);
            printf("connection from %s, port %d\n",
                   Inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)),
                   ntohs(cliaddr.sin_port));
    
            ticks = time(NULL);
            snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
            Write(connfd, buff, strlen(buff));
    
            Close(connfd);
        }
    }

服务器监听9999端口,新连接创建后,发出当前时间,随后调用close()关闭链接。

客户端部分:

int main(int argc, char **argv)
    {
        int                    sockfd, n;
        socklen_t            len;
        char                recvline[MAXLINE + 1];
        struct sockaddr_in    servaddr, cliaddr;
    
        if (argc != 2)
            err_quit("usage: a.out <IPaddress>");
    
        if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
            err_sys("socket error");
    
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port   = htons(9999);    /* daytime server */
        if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
            err_quit("inet_pton error for %s", argv[1]);
    
        if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
            err_sys("connect error");
    
        len = sizeof(cliaddr);
        Getsockname(sockfd, (SA *) &cliaddr, &len);
        printf("local addr: %s\n",
               Sock_ntop((SA *) &cliaddr, sizeof(cliaddr)));
    
        while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
            recvline[n] = 0;    /* null terminate */
            if (fputs(recvline, stdout) == EOF)
                err_sys("fputs error");
        }
    
        if (n < 0)
            err_sys("read error");
    
        exit(0);
    }

客户端没有及时地调用close()调用

咱们修改客户端调用close()的处理流程。当read()==0时,阻塞睡眠。等待一段时间后,发送SIGINT使进程退出,至关于调用close()。注意到如下的现象:

  • 服务端SOCKET处于FIN-WAIT-2状态时,发送SIGINT信号使客户端退出,客户端发送FIN,服务端回复FIN-ACK。此时,按正常流程结束连接。

  • 服务端SOCKET等待FIN-WAIT-2状态超时后,客户端发送FIN,服务端回复RST结束连接。

Orphan Socket: 从应用程序来看,此条socket链接已经收发数据完毕,关闭了此链接,可是linux内核中为了完成正常的tcp协议(好比缓冲区中的数据)转换,会在内核的tcp协议层继续维护这些sock状态,直至系统回收。处于此种状态下的socket就是orphan socket。

分析:

  • 服务端SOCKET关闭后,没有对这一SOCKET的引用。这一SOCKET进入到“孤儿SOCKET“的状态。孤儿Socket存在时,系统协议栈负责完成后续的FIN流程。当孤儿Socket超时后,系统协议栈将不存在这一Socket的信息。客户端此时发送FIN,将收到RST应答。

相关文章
相关标签/搜索