Linux TCP通訊出現CLOSE_WAIT後導致服務端進程掛掉

來源:互聯網
上載者:User

在前文中講述了Linux服務端TCP通訊出現CLOSE_WAIT狀態的原因,這篇文章主要通過一個執行個體示範它個一個“惡劣”影響:直接使服務端進程Down掉。

CentOS服務端建立監聽連接埠

1 CentOS服務端建立監聽連接埠

如上圖所示,在虛擬機器CentOS7伺服器(192.168.1.178)中開啟一個終端介面,建立8000連接埠的監聽服務(PID:13035)。所用代碼如下,和上一篇文章中的程式大體一樣,只是多了一個SIGPIPE訊號處理以及自動回複(Auto response from server.)部分。

 代碼如下 複製代碼

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <signal.h>

//Whether add a signal handle.
#define SIGNAL_HANDLE 0

void sig_handle( int signal )
{
    printf( "Receive a signal=[%d].\n", signal );
    return;
}

int main( int argc, char **argv )
{
    int server_sockfd;
    int client_sockfd;
    int len;
    int llOpt = 1;
    struct sockaddr_in my_addr;
    struct sockaddr_in remote_addr;
    int sin_size;
    char buf[BUFSIZ];
    memset( &my_addr, 0, sizeof(my_addr) );
    my_addr.sin_family = AF_INET;
    my_addr.sin_addr.s_addr = INADDR_ANY;
    my_addr.sin_port = htons(8000);

    #if SIGNAL_HANDLE
    struct sigaction new_act, old_act;
    new_act.sa_handler = sig_handle;
    new_act.sa_flags = 0;
    sigemptyset( &new_act.sa_mask );
    sigaction( SIGPIPE, &new_act, &old_act );
    sigaction( SIGINT, &new_act, &old_act );   
    #endif

    if( ( server_sockfd = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 )
    { 
        perror("socket");
        return 1;
    }

    if( setsockopt( server_sockfd, SOL_SOCKET, SO_REUSEADDR, &llOpt, sizeof(llOpt) ) ) {
        close(server_sockfd);
        return errno;
    }

    if( bind( server_sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr) ) < 0 )
    {
        perror( "bind" );
        return 1;
    }

    listen( server_sockfd, 5 );

    sin_size = sizeof( struct sockaddr_in );

    printf( "Server is listening with pid=[%d].\n", getpid() );

    while(1)
    {
        if( ( client_sockfd = accept( server_sockfd, (struct sockaddr *)&remote_addr, &sin_size ) ) < 0 )
        {
            perror( "accept" );
            return 1;
        }
        //Print the ip address and port of client.
        printf( "Accept client[%s:%u].\n", inet_ntoa(remote_addr.sin_addr), ntohs(remote_addr.sin_port) );

        send( client_sockfd, "Auto response from server.", strlen("Auto response from server."), 0 );

        memset( buf, 0x00, BUFSIZ );
        while( ( len = recv( client_sockfd ,buf, BUFSIZ, 0) ) > 0 )
        {
            buf[len]='\0';
            printf( "Message from client=[%s]\n", buf );
        }
        close( client_sockfd );
    }
    close( server_sockfd );
    return 0;
}

2 在Linux中利用telnet命令建立一個用戶端

建立一個shell指令碼netstat_nap.sh,裡面只包含一條有效命令netstat -nap|head -n 2;netstat -nap|grep 8000。

再開啟一個Linux終端介面,然後輸入命令telnet 192.168.1.177 8000作為用戶端建立與服務端的TCP串連。這時執行指令碼./netstat_nap.sh可以看到Linux用戶端(PID:13045)和服務端(PID:13035)的TCP通訊已經變成ESTABLISHED狀態,效果如下圖所示:

3 在Windows中利用telnet命令建立一個用戶端

在Windows主機(192.168.1.110)中開啟一個PowerShell終端介面,然後輸入命令telnet 192.168.1.177 8000作為用戶端建立與Linux服務端的TCP串連。

這時執行指令碼./netstat_nap.sh,可以看到Windows用戶端(連接埠:64012)和服務端(PID:13035)的TCP通訊已經變成ESTABLISHED狀態。同時使用命令lsof -i:8000,可以看到進程開啟的檔案。

4 直接關閉Windows telnet用戶端介面並使用Wireshark抓包

在直接關閉telnet介面後,繼續使用netstat_nap.sh指令碼和lsof命令發現剛才建立的TCP通訊出現了CLOSE_WAIT的狀態。

在等待2分鐘後,在Windows中使用Wireshark抓包發現由於用戶端發送了RST+ACK報文給Linux服務端,所以二者的TCP鏈路已經被複位了:

在Windows中使用Wireshark抓包

這時在Linux中再次使用netstat_nap.sh指令碼和lsof命令發現CLOSE_WAIT的狀態已經不存在了。

5 關閉Linux telnet用戶端

在Windows關閉telnet用戶端介面並發送RST+ACK報文後,關閉小節2中在Linux中開啟的telnet用戶端。這時Linux服務端進程會執行第90行處的close()函數,也即執行正常四次揮手關閉TCP串連。

接著Linux服務端進程繼續從核心中已完成串連隊列中取出已完成串連,這樣之前小節3中Windows telnet建立的用戶端串連被讀取。如下圖所示,服務端進程列印了第80行出的資料(Accept client[192.168.1.110:64012].),但是服務端進程卻掛掉了。

CentOS服務端建立監聽連接埠

這時在Linux中再次使用netstat_nap.sh指令碼和lsof命令:

 

6 原因分析

由於Windows用戶端的TCP鏈路在小節4中由於RST的緣故而關閉了,沒有讀端。那麼當Linux服務端執行82行的send()函數時,向之前的socket描述符發送26位元組的報文資料時,會收到核心發送過來的SIGPIPE訊號,導致服務端進程預設關閉。

因此,如果想捕捉到這個SIGPIPE訊號的話,可以將程式17行的SIGNAL_HANDLE宏定義值改成1,那麼就會得到如下圖所示的情況(進程能正常運行了)。

 

7 問題延伸

如果在第4小節中關閉Windows用戶端介面後,又直接如第5小節所示關閉Linux telnet用戶端介面,那麼又會出現什麼情況呢?於是又重新做了一遍測試,流程同上,下面是測試結果以及分析。

先用netstat和lsof命令查看TCP服務狀態,發現監聽服務正常:

 

然後分別用TCPDUMP和Wireshark抓取TCP通訊包,截取如下所示。可以發現在Linux telnet用戶端完成四次揮手後,服務端進程繼續向之前Windows telnet用戶端建立的socket描述符發送26位元組的報文資料。

因為Windows用戶端此時處於FIN_WAIT2狀態(Linux服務端處於CLOSE_WAIT狀態),所以服務端能繼續發其發送資料(即圖中的PUSH+ACK報文),接著Windows用戶端回應RST+ACK報文,從而兩者的TCP鏈路複位。

在Linux中使用TCPDUMP抓包

在Windows中使用Wireshark抓包

這樣Linux服務端進程還是能夠正常執行監聽任務:

 

8 其它

網上有人把這種用戶端或者服務端異常關閉的串連叫做TCP半關閉(Half-Close),例如網線拔掉、突然斷電等,此時對端串連仍認為雙方串連處於開啟中。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.