調用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應答。

相關文章
相關標籤/搜索