如何編寫一個穩定的網絡程序(TCP)


本節我們看一下怎樣才能編寫一個基於TCP穩定的客戶端或者服務器程序,主要以試驗抓包的方式觀察數據包的變化,對網絡中出現的多種情況進行分析,分析網絡程序中常用的技術及它們出現的原因,在之后的編程中能早一點意識到這些潛在問題。實例代碼如下: client.c 和server.c  因在試驗過程中代碼有所改動,本實例代碼僅僅是參考。

#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#define PORT 6666
#define MAXSIZE 1024
void str_cli(FILE *, int);
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        fprintf(stderr, "./client IP\n");
        return 1;
    }
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(PORT);
    inet_pton(AF_INET, argv[1], &serveraddr.sin_addr);
    connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
    //FILE * fp = fopen("./aa","r");
    //str_cli(fp, fd);
    sleep(60);
    close(fd);
    exit(0);
}

void str_cli(FILE * fp, int fd)
{
    char sendbuff[MAXSIZE], recvbuff[MAXSIZE];
    bzero(sendbuff, MAXSIZE);
    bzero(recvbuff,MAXSIZE);
    while (fgets(sendbuff, MAXSIZE, fp) != NULL)
    {
        write(fd, sendbuff, strlen(sendbuff));
        printf("hello\n");
        /*  if(read(fd, recvbuff, MAXSIZE) == 0)
        {   
            if(errno == ECONNRESET)
            {
                fprintf(stderr, "reconnect\n");     
            }
            fprintf(stderr, "server terminated!\n");
            exit(1);
        }*/
        fputs(recvbuff, stdout);
        bzero(sendbuff, MAXSIZE);
        bzero(recvbuff,MAXSIZE);
    }
}
View Code
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#define PORT 6666
#define MAXSIZE 1024
void str_ser(int);
int main()
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serveraddr, clientaddr;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    //inet_aton("15.15.182.182",&serveraddr.sin_addr);
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(PORT);
    bind(fd,(const struct sockaddr *)&serveraddr, sizeof(serveraddr));
    listen(fd, 5);
    socklen_t len = sizeof(clientaddr);
    for(;;)
    {
    //  int clientfd = accept(fd, (struct sockaddr *)&clientaddr, &len);
    /*  if(fork() == 0)
        {
            close(fd);
            str_ser(clientfd);
            exit(0);
        }
        */
    //  close(clientfd);
        sleep(3);
    }
    return 0;
}

void str_ser(int clientfd)
{
    char buff[MAXSIZE];
    size_t n;
    bzero(buff, MAXSIZE);
    sleep(2000);
    while( (n = read(clientfd, buff, MAXSIZE)) > 0)
    {

            write(clientfd, buff, n);
            bzero(buff, MAXSIZE);
    }
    if(n <= 0)
        fprintf(stderr, "read error\n");

}
View Code

客戶端遇到情形如下:

客戶端連接一個沒有在監聽的主機:

這里必須闡述一個事實,TCP的三次握手在listen之后就可以完成,可以將accept注釋掉進行測試,listen系統調用之后會建立兩個隊列 listen 和accept ,listen隊列就是當在三次握手過程中服務器收到了客戶端發送的SYN時,就會將客戶端結構放入listen隊列,然后向客戶端回應一個SYN+ACK,  當收到客戶端最后的ACK之后,就會將這個客戶端相關結構放入accept隊列等待accept系統調用將它從隊列中取出。

若主機沒有監聽,客戶端直接鏈接的話:

 CLIENT]# tcpdump -i eth0 -A -vvn tcp port 6666 and host 192.168.179.128
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes

13:40:52.874157 IP (tos 0x0, ttl 64, id 59618, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.179.129.21552 > 192.168.179.128.ircu-2: Flags [S], cksum 0xe881 (incorrect -> 0xffde), seq 1309016102, win 29200, options [mss 1460,sackOK,TS val 1217587396 ecr 0,nop,wscale 7], length 0
E..<..@.@.i.........T0.
N..&......r............
H...........
13:40:52.874425 IP (tos 0x0, ttl 64, id 32912, offset 0, flags [DF], proto TCP (6), length 40)
    192.168.179.128.ircu-2 > 192.168.179.129.21552: Flags [R.], cksum 0x0b16 (correct), seq 0, ack 1309016103, win 0, length 0
E..(..@.@............
T0....N..'P.............

我的目的主機是192.168.179.128, 當來自192.168.179.129的客戶端發起連接請求時,由於服務器相應端口並沒有在監聽,所以在收到客戶端的SYN之后,緊接着就由內核發送了一個RST連接復位發送給客戶端。那么客戶端是否有辦法知道這種事實?答案是肯定的 看一下manpage對connect的描述:

       ECONNREFUSED
              No-one listening on the remote address.

當收到RST時, 客戶端的connect調用會返回錯誤並將errno值為ECONNREFUSED,此時就可以判斷處遠程server並沒有監聽端口,此時客戶端可以選擇退出或者稍后重試等等。

客戶端連接一個網絡達不到的主機:

這里又可以分為兩種情況:  1. 探測型的網絡達不到ETIMEDOUT (如可能就是網絡中的某個主機)   2. 已知型的網絡達不到(如和客戶端在同一局域網主機但是沒有開機路由回饋ICMP) EHOSTUNREACH

這里隨便找了一個ping長時間沒有反饋的網絡中的某個主機,用客戶端程序連接它:

 CLIENT]# tcpdump -i eth0 -A -vvn tcp port 6666 and host 122.155.44.22
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
13:45:37.932632 IP (tos 0x0, ttl 64, id 60109, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.179.129.22126 > 122.155.44.22.ircu-2: Flags [S], cksum 0x1b0a (incorrect -> 0x7163), seq 2781849738, win 29200, options [mss 1460,sackOK,TS val 1217872455 ecr 0,nop,wscale 7], length 0
E..<..@.@.5.....z.,.Vn.
..........r..
.........
H.BG........
13:45:38.934330 IP (tos 0x0, ttl 64, id 60110, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.179.129.22126 > 122.155.44.22.ircu-2: Flags [S], cksum 0x1b0a (incorrect -> 0x6d79), seq 2781849738, win 29200, options [mss 1460,sackOK,TS val 1217873457 ecr 0,nop,wscale 7], length 0
E..<..@.@.5.....z.,.Vn.
..........r..
.........
H.F1........
13:45:40.941331 IP (tos 0x0, ttl 64, id 60111, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.179.129.22126 > 122.155.44.22.ircu-2: Flags [S], cksum 0x1b0a (incorrect -> 0x65a2), seq 2781849738, win 29200, options [mss 1460,sackOK,TS val 1217875464 ecr 0,nop,wscale 7], length 0
E..<..@.@.5.....z.,.Vn.
..........r..
.........
H.N.........
13:45:44.949506 IP (tos 0x0, ttl 64, id 60112, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.179.129.22126 > 122.155.44.22.ircu-2: Flags [S], cksum 0x1b0a (incorrect -> 0x55fa), seq 2781849738, win 29200, options [mss 1460,sackOK,TS val 1217879472 ecr 0,nop,wscale 7], length 0
E..<..@.@.5.....z.,.Vn.
..........r..
.........
H.].........

可以看到客戶端會不斷嘗試與服務器握手,間隔時間並不確定,  但是它並不會一直這樣下去直到connet放棄  connect就會返回ETIMEDOUT。這種形式的錯誤路由器並不返回ICMP錯誤。

情況2: 這種情況會很快知道並由路由器返回ICMP錯誤 告知主機不可達。接下來不會再發送SYN 嘗試連接。 connect返回errno == EHOSTUNREACH.   

對由這兩種情況下errno都可以捕捉到,但是timeout發生的時間稍微有些長,這里可以將connect設為非阻塞,利用select探測描述符是否可讀可寫,再getsockopt得到相應結果:

SetNONBlock();
        tval.tv_sec = timeout;
        tval.tv_usec = 100;
        fd_set wfd;
        FD_ZERO(&wfd);
        FD_SET(sockfd, &wfd);
        int resconn = connect(sockfd, (const sockaddr *)&seraddr, sizeof(seraddr));
        if(resconn == 0)
        {
            write(1, "Connection success.\n",50);
            return true;
        }
        if (resconn == -1)
        {
            if(errno == EINPROGRESS)
            {
                int nready = select(sockfd+1, NULL, &wfd, NULL, &tval);
                if(nready == -1)
                {
                    close(sockfd);
                    HandleError("Select");
                }else if(nready == 0)
                {
                    write(2, "Connect Timeout!\n", 50);
                    close(sockfd);
                    exit(0);
                }else
                {
                    int err;
                    socklen_t len = sizeof(err);
                    if(FD_ISSET(sockfd, &wfd))
                    {
                        if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &len) == -1)
                        {
                            close(sockfd);
                            HandleError("Getsockopt");
                        }
                        if(err == 0)
                        {
                            write(1, "Connect success.\n", 50);
                            return true;
                        }else{
                            close(sockfd);
                            errno = err;
                            HandleError("Connect");
                        }
View Code

客戶端已經連接到服務器沒有發送數據 這時網絡突然斷了(或server主機斷電):

CLIENT]# tcpdump -i eth0 -A -vvn tcp port 6666 and host 192.168.179.128
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes

13:58:28.613040 IP (tos 0x0, ttl 64, id 25556, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.179.129.21556 > 192.168.179.128.ircu-2: Flags [S], cksum 0xe881 (incorrect -> 0x74b8), seq 1440245611, win 29200, options [mss 1460,sackOK,TS val 1218643135 ecr 0,nop,wscale 7], length 0
E..<c.@.@...........T4.
U.gk......r............
H...........
13:58:28.613408 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.179.128.ircu-2 > 192.168.179.129.21556: Flags [S.], cksum 0x87af (correct), seq 466362604, ack 1440245612, win 14480, options [mss 1460,sackOK,TS val 1763749 ecr 1218643135,nop,wscale 7], length 0
E..<..@.@.Ri.........
T4.. .U.gl..8............
....H.......

這里可以看到 client並不知道網絡中出現異常,因為它並沒有發送數據,server斷開網絡或者斷電並沒有跟客戶端打招呼,這里TCP協議中的保活計時器就會發現這種情況, 套接字選項中的keeplive。但是這個時間間隔很長不能及時發現,雖然這個值可以改動,一般這種情況就需要心跳檢測了,這就是心跳檢測出現的原因。心跳就是對於長連接而言,客戶端與服務器不斷的進行少量的數據交互來保證彼此的存在。(如果這時開始發送數據那么就會不斷重傳 下述情形)

客戶端已經連接到服務器並在發送東西,這時網絡斷了或者server主機突然斷電:

這里測試就是一個簡單的Echo模式:客戶端發送字符串然后服務器接收后在發送回來 如下抓包結果:

14:13:14.139432 IP (tos 0x0, ttl 64, id 54328, offset 0, flags [DF], proto TCP (6), length 62)
    192.168.179.128.ircu-2 > 192.168.179.129.21568: Flags [P.], cksum 0xd461 (correct), seq 141:151, ack 151, win 114, options [nop,nop,TS val 2649286 ecr 1219528661], length 10
E..>.8@.@.~..........       //服務器向客戶端發送數據
T@..+........r.a.....
.(l.H...sdfsdfsdf

14:13:14.139509 IP (tos 0x0, ttl 64, id 60816, offset 0, flags [DF], proto TCP (6), length 52)
    192.168.179.129.21568 > 192.168.179.128.ircu-2: Flags [.], cksum 0xe879 (incorrect -> 0xeba2), seq 151, ack 151, win 229, options [nop,nop,TS val 1219528662 ecr 2649286], length 0
E..4..@.@.d.........T@.       //客戶端確認
......+......y.....
H....(l.
14:13:16.140328 IP (tos 0x0, ttl 64, id 60817, offset 0, flags [DF], proto TCP (6), length 62)
    192.168.179.129.21568 > 192.168.179.128.ircu-2: Flags [P.], cksum 0xe883 (incorrect -> 0xcc12), seq 151:161, ack 151, win 229, options [nop,nop,TS val 1219530663 ecr 2649286], length 10
E..>..@.@.d.........T@.          //客戶端再發送   這時服務器已經斷開了網絡
......+............
H....(l.sdfsdfsdf

14:13:16.342204 IP (tos 0x0, ttl 64, id 60818, offset 0, flags [DF], proto TCP (6), length 62)
    192.168.179.129.21568 > 192.168.179.128.ircu-2: Flags [P.], cksum 0xe883 (incorrect -> 0xcb49), seq 151:161, ack 151, win 229, options [nop,nop,TS val 1219530864 ecr 2649286], length 10
E..>..@.@.d.........T@.                                              //看它的序號     
......+............
H..p.(l.sdfsdfsdf

14:13:16.542401 IP (tos 0x0, ttl 64, id 60819, offset 0, flags [DF], proto TCP (6), length 62)
    192.168.179.129.21568 > 192.168.179.128.ircu-2: Flags [P.], cksum 0xe883 (incorrect -> 0xca80), seq 151:161, ack 151, win 229, options [nop,nop,TS val 1219531065 ecr 2649286], length 10
E..>..@.@.d.........T@.                                          //序號不變    接下來都是不變的
......+............
H..9.(l.sdfsdfsdf

14:13:16.945409 IP (tos 0x0, ttl 64, id 60820, offset 0, flags [DF], proto TCP (6), length 62)
    192.168.179.129.21568 > 192.168.179.128.ircu-2: Flags [P.], cksum 0xe883 (incorrect -> 0xc8ed), seq 151:161, ack 151, win 229, options [nop,nop,TS val 1219531468 ecr 2649286], length 10
E..>..@.@.d.........T@.
......+............
H....(l.sdfsdfsdf

14:13:17.751262 IP (tos 0x0, ttl 64, id 60821, offset 0, flags [DF], proto TCP (6), length 62)
    192.168.179.129.21568 > 192.168.179.128.ircu-2: Flags [P.], cksum 0xe883 (incorrect -> 0xc5c7), seq 151:161, ack 151, win 229, options [nop,nop,TS val 1219532274 ecr 2649286], length 10
E..>..@.@.d.........T@.
......+............
H....(l.sdfsdfsdf


14:13:19.365418 IP (tos 0x0, ttl 64, id 60822, offset 0, flags [DF], proto TCP (6), length 62)
    192.168.179.129.21568 > 192.168.179.128.ircu-2: Flags [P.], cksum 0xe883 (incorrect -> 0xbf79), seq 151:161, ack 151, win 229, options [nop,nop,TS val 1219533888 ecr 2649286], length 10
E..>..@.@.d.........T@.
......+............
H..@.(l.sdfsdfsdf
14:13:22.589323 IP (tos 0x0, ttl 64, id 60823, offset 0, flags [DF], proto TCP (6), length 62)
    192.168.179.129.21568 > 192.168.179.128.ircu-2: Flags [P.], cksum 0xe883 (incorrect -> 0xb2e1), seq 151:161, ack 151, win 229, options [nop,nop,TS val 1219537112 ecr 2649286], length 10
E..>..@.@.d.........T@.
......+............
H....(l.sdfsdfsdf

這里分析結果可以看到,client后續一直再重傳了,因為當server之間斷開網絡時,client再次發送數據 此時已經收不到server的ACK確認信息並且以后任何信息都接收不到了,client就反復的重傳大約會堅持8-15min。當網絡恢復正常時 又可以重新發送(TCP協議的實現)。 那么client怎么才能知道這件事呢?記得套接字選項里有sendtimeout 和recvtimeoutout 但是sendtimeout並不是指收到ACK的超時設定。那么一直收不到ACK造成重傳的這個問題到底要怎么辦呢?我這里試驗方式如下:1. 將文件描述符改為非阻塞行不行?   2.  加上套接字選項send 超時行不行?  3. 這種情況一直send會造成EPIPE信號產生嗎?  4. 什么時候才會由這個EPIPE的信號產生?

#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#define PORT 6666
#define MAXSIZE 1024
void str_cli(FILE *, int);
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        fprintf(stderr, "./client IP\n");
        return 1;
    }
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(PORT);
    inet_pton(AF_INET, argv[1], &serveraddr.sin_addr);
    struct timeval tv;
    tv.tv_sec = 5;
    tv.tv_usec = 12;
    setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
    connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
    FILE * fp = fopen("./aa","r");
    str_cli(fp, fd);
    sleep(60);
    close(fd);
    exit(0);
}

void str_cli(FILE * fp, int fd)
{
    int flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, O_NONBLOCK | flags);
    char sendbuff[MAXSIZE], recvbuff[MAXSIZE];
    bzero(sendbuff, MAXSIZE);
    bzero(recvbuff,MAXSIZE);
    fgets(sendbuff, MAXSIZE, fp);
    while ( 1)
    {
        if(-1==send(fd, sendbuff, strlen(sendbuff), 0))
        {
            fprintf(stderr, "%s\n", strerror(errno));
        }
        fprintf(stderr, "p>>>>>\n");
          if(read(fd, recvbuff, MAXSIZE) == 0)
        {   
            if(errno == ECONNRESET)
            {
                fprintf(stderr, "reconnect\n");     
            }
            fprintf(stderr, "server terminated!\n");
            exit(1);
        }
        fputs(recvbuff, stdout);
       // bzero(sendbuff, MAXSIZE);
        bzero(recvbuff,MAXSIZE);
        sleep(2);
    }
}
View Code
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <assert.h>
#define PORT 6666
#define MAXSIZE 1024
void str_ser(int);
int main()
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serveraddr, clientaddr;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    //inet_aton("15.15.182.182",&serveraddr.sin_addr);
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(PORT);
    bind(fd,(const struct sockaddr *)&serveraddr, sizeof(serveraddr));
    listen(fd, 5);
    socklen_t len = sizeof(clientaddr);
    for(;;)
    {
        int clientfd = accept(fd, (struct sockaddr *)&clientaddr, &len);
        str_ser(clientfd);
        close(clientfd);
        sleep(3);
    }
    return 0;
}

void str_ser(int clientfd)
{
    char buff[MAXSIZE];
    size_t n;
    bzero(buff, MAXSIZE);
    int num = 1;
    //sleep(2000);
    while( (n = read(clientfd, buff, MAXSIZE)) > 0)
    {
        num++;
        if (num == 5)
        {
            assert(num > 4);
        }
            write(clientfd, buff, n);
            bzero(buff, MAXSIZE);
    }
    if(n <= 0)
        fprintf(stderr, "read error\n");

}
View Code

1.   將文件描述符設為非阻塞.

  在斷開網絡后,仍然可以send,一段時間內並不會返回-1,也不會造成epipe信號產生。直到tcp已經放棄重傳后,send 返回-1,errno=EHOSTUNREACH 也可能是ETIMEDOUT。如果這里使用IO復用的話,返回可讀時間read也會返回-1,errno相同。

2. 加上套接字選項sendtimeout

  這種情況並不會發生send 超時的異常,這里套接字選項中的超時只是應對延遲發送的,並不能保證send在超時時間內收到來自對方的確認ACK。

3. 斷開網絡一直send不會產生EPIPE異常

  那什么時候才會產生這個異常信號呢?  這里在發送過程中,將server進程殺掉注意這里目標主機還能達到只是server進程死了,讓client並不處理server發來的FIN 或者RST復位 client仍然再次發送數據,這時client就會返回EPIPE異常。默認情況下進程會終止,服務器常常會發生這種情況可將此信號忽略。

試驗結果就是即使改為非阻塞,send也並不會馬上返回錯誤,仍然會繼續往內核緩沖區寫,因為沒有收到對方的確認這時會重新發送一段時間,后續如果網絡恢復那么就會繼續正常發送。這個重傳時間為8-15min。超過這個時間那么就會產生路由發送ICMP錯誤 告訴client已經路由不到目的主機了  client send就會返回-1   errno = EHOSTUNREACH網絡不可達的錯誤(也可能沒有收到ICMP,返回ETIMEDOUT)。(那么還有一種情況就是重傳還沒有超時間,但是內核的緩沖區被我們寫滿了,那么send也會返回-1, errno=ENOBUFS,manpage中有這個說法)。

對於這種情況無論是客戶端還是服務端都會發生,對於服務端影響還是很大的,例如如果客戶端斷網服務器沒有發現就一直保留相關client的數據結構並不斷嘗試重傳。這種情形在真實的網絡中常有發生,對於服務器對客戶端發送數據時一直重傳的話那么效率必定會下降。其中心跳可以讓我們發現這個問題,例如我們規定心跳包發送2min內沒有收到反饋,那么就認為是對方斷網了,不必一直等待重傳 可將它直接close 再刪除相關數據結構。那么這里還有個細節就是close時 發送FIN可能也不會得到對方確認啊!? 抓包截這看: client與服務器斷開網絡之后 我在client又send多次數據之后close連接。

10:46:49.231527 IP (tos 0x0, ttl 64, id 31328, offset 0, flags [DF], proto TCP (6), length 62)
    192.168.179.129.61420 > 192.168.179.128.ircu-2: Flags [P.], cksum 0xe883 (incorrect -> 0xa82e), seq 71:81, ack 71, win 229, options [nop,nop,TS val 71250752 ecr 2765237], length 10
E..>z`@.@..............
...Z...2...........      此處為重傳 看這里還是那一個包
.?3@.*1.sdfsdfsdf

10:47:07.799865 IP (tos 0x0, ttl 64, id 31329, offset 0, flags [DF], proto TCP (6), length 272)
    192.168.179.129.61420 > 192.168.179.128.ircu-2: Flags [FP.], cksum 0xe955 (incorrect -> 0x6d95), seq 81:301, ack 71, win 229, options [nop,nop,TS val 71269321 ecr 2765237], length 220
E...za@.@..3...........   //這里close 連接 剛才數據還在一直send 所以會有很多的數據一次被發送出去
...d...2.....U.....
.?{..*1.sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf

10:47:15.151565 IP (tos 0x0, ttl 64, id 31330, offset 0, flags [DF], proto TCP (6), length 282)
    192.168.179.129.61420 > 192.168.179.128.ircu-2: Flags [FP.], cksum 0xe95f (incorrect -> 0x3931), seq 71:301, ack 71, win 229, options [nop,nop,TS val 71276672 ecr 2765237], length 230
E...zb@.@..(........... //重傳
...Z...2....._.....
.?...*1.sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf
sdfsdfsdf

再看close時網絡狀態轉換:

這里可以看到client發送一個FIN后會立即進入FIN-WAIT-1狀態,當收到ACK之后就會進入FIN-WAIT-2狀態。由抓包結果FIN發送之后並沒有等到確認,那么還是會重傳,再看網絡狀態:

 CLIENT]$ netstat -an | grep 6666
tcp        0    231 192.168.179.129:61420   192.168.179.128:6666 FIN_WAIT1  
unix  3      [ ]         STREAM     CONNECTED     26666    
 CLIENT]$ netstat -an | grep 6666
tcp        0    231 192.168.179.129:61420   192.168.179.128:6666    FIN_WAIT1  
unix  3      [ ]         STREAM     CONNECTED     26666    
 CLIENT]$ netstat -an | grep 6666
tcp        0    231 192.168.179.129:61420   192.168.179.128:6666    FIN_WAIT1  
unix  3      [ ]         STREAM     CONNECTED     26666  

這里的FIN_WAIT1會維持一段時間,大約10秒左右就沒有了,最后並不會進入TIME_WAIT狀態,估計內核會把所有數據和狀態清空。即使close掉還是造成重傳,但是應用層數據已經清空了,剩下的只是內核在處理,但是如果你在close之后恰好網絡又恢復了正常,最后的FIN和push都被收到的話,這種情況對端也會正常接收數據並發送FIN。

客戶端已連接服務器但並沒有在發送數據 server進程崩潰 server主機正常關機(LINUX):

    192.168.179.129.21604 > 192.168.179.128.ircu-2: Flags [.], cksum 0xe879 (incorrect -> 0xcb3b), seq 1, ack 1, win 229, options [nop,nop,TS val 1221249014 ecr 4369660], length 0
E..4..@.@.J.........Td.
.C.'/........y.....
H....B..
14:42:15.546958 IP (tos 0x0, ttl 64, id 44816, offset 0, flags [DF], proto TCP (6), length 52)
    192.168.179.128.ircu-2 > 192.168.179.129.21604: Flags [F.], cksum 0x796d (correct), seq 1, ack 1, win 114, options [nop,nop,TS val 4390716 ecr 1221249014], length 0
E..4..@.@..`.........
Td/....C.'...rym.....
.B.<H...
14:42:15.547715 IP (tos 0x0, ttl 64, id 2005, offset 0, flags [DF], proto TCP (6), length 52)
    192.168.179.129.21604 > 192.168.179.128.ircu-2: Flags [.], cksum 0xe879 (incorrect -> 0x26ba), seq 1, ack 2, win 229, options [nop,nop,TS val 1221270070 ecr 4390716], length 0
E..4..@.@.J.........Td.
.C.'/........y.....
H..6.B.<

由抓包結果看即使進程死了但是內核會回收資源告知已關閉。客戶端或者服務器都可以當正常處理。

client正在發送數據   server進程崩潰server正常關機:

192.168.179.129.21580 > 192.168.179.128.ircu-2: Flags [P.], cksum 0xe883 (incorrect -> 0x2c64), seq 31:41, ack 31, win 229, options [nop,nop,TS val 1220079889 ecr 3198519], length 10
E..>..@.@.=.........TL.
...*r..C...........
H....0.7sdfsdfsdf

14:22:25.423049 IP (tos 0x0, ttl 64, id 14522, offset 0, flags [DF], proto TCP (6), length 52)
    192.168.179.128.ircu-2 > 192.168.179.129.21580: Flags [.], cksum 0x3c92 (correct), seq 31, ack 41, win 114, options [nop,nop,TS val 3200561 ecr 1220079889], length 0
E..48.@.@............
TLr..C...4...r<......
.0.1H...
14:22:25.744845 IP (tos 0x0, ttl 64, id 14523, offset 0, flags [DF], proto TCP (6), length 52)
    192.168.179.128.ircu-2 > 192.168.179.129.21580: Flags [F.], cksum 0x3b3f (correct), seq 31, ack 41, win 114, options [nop,nop,TS val 3200899 ecr 1220079889], length 0
E..48.@.@............
TLr..C...4...r;?.....
.0..H...

這時也會收到斷開信息,但是如果接收端對這個斷開忽略掉 仍堅持send的話 那么就會造成EPIPE信號  這個信號默認情況下是終止進程,對於服務器來講當然不希望這樣,一個客戶端異常關閉了服務器仍然堅持寫的話就造成了服務器進程退出,在服務器程序中往往重寫這個信號處理函數。服務器端也可能檢測到這個文件描述符異常在將其關閉。

總結:

  宗上的幾種情況都是實際網絡中容易發生的,其實無論是客戶端和服務器都應該注意這些細節問題,當編寫服務器或者客戶端時考慮這些情形才會讓程序更加健壯。尤其對於服務器開發而言,這些知識都十分重要。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM