listen的参数backlog的意义

实验环境:Ubuntu16.04,内核版本:4.4.0-59-generic
 
根据man listen得到的解释如下:
 
backlog参数定义了存放pending状态(挂起、护着搁置)的连接的队列的最大长度;如果在队列满的时候,一个连接请求到达,客户端可能会收到一个错误:ECONREFUSED。
 
然后man listen的下面有一个小提示:
 
现在backlog这个参数指示的是存放已经建立连接(established)并等待被accept的sockets的队列的长度。
没有完成的socket队列的长度可以通过 /proc/sys/net/ipv4/tcp_max_syn_backlog 这个参数来设置。
如果backlog参数大于 /proc/sys/net/core/somaxconn 的值,那么该值将被自动截断为somaxconn的值,它的值默认是128。
 
下面是我做的一个关于backlog的小实验:
tcpSvr端调用完listen后,sleep,
客户端根据参数,开启n个线程,每个线程都尝试与tcpSvr建立连接。
 1 /*
 2  * gcc -std=c99 -o tcpCli ./tcpCli.c -lpthread
 3  */
 4 #include <unistd.h>
 5 #include <sys/socket.h>
 6 #include <sys/types.h>
 7 #include <netinet/in.h>
 8 #include <arpa/inet.h>
 9 #include <stdio.h>
10 #include <string.h>
11 #include <stdlib.h>
12 #include <errno.h>
13 #include <pthread.h>
14 
15 const int PORT = 7766;
16 
17 void* func(void *arg)
18 {
19     int fd = socket(AF_INET, SOCK_STREAM, 0);
20     if (fd == -1)
21     {
22         perror("socket");
23         return (void*)0;
24     }
25 
26     struct sockaddr_in addr;
27     memset(&addr, 0, sizeof(addr));
28     addr.sin_family = AF_INET;
29     addr.sin_addr.s_addr = inet_addr("127.0.0.1");
30     addr.sin_port = htons(PORT);
31 
32     // get send buffer size
33     int iWBufSize;
34     socklen_t optLen = sizeof(iWBufSize);
35     getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &iWBufSize, &optLen);
36     printf("Write buffer size = %d\n", iWBufSize);
37 
38     int iRBufSize;
39     optLen = sizeof(iRBufSize);
40     getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &iRBufSize, &optLen);
41     printf("Read buffer size = %d\n", iRBufSize);
42 
43     if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) != 0)
44     {
45         perror("connect");
46         return (void*)0;
47     }
48 
49     char buf[1500] = "hello world!";
50     int iCount = 0;
51     while (1)
52     {
53         sprintf(buf, "%d", iCount);
54         int iRet = send(fd, buf, strlen(buf), 0);
55         if (iRet == -1)
56         {
57             perror("sendto");
58             break;
59         }
60         else
61         {
62             // printf("Send data len [%d]\n", iRet);
63             iCount++;
64         }
65         if (iCount % 100 == 0)
66         {
67             // printf("Send package count %d\n", iCount);
68             sleep(5);
69         }
70     }
71     printf("Send package count %d\n", iCount);
72     close(fd);
73 
74     return (void*)0;
75 }
76 int main(int argc, char **argv)
77 {
78     if (argc != 2)
79     {
80         printf("Usage:%s thread_num\n", argv[0]);
81         return 0;
82     }
83 
84     int iThreadNum = atoi(argv[1]);
85     printf("ThreadNum [%d]\n", iThreadNum);
86     pthread_t *pTid = (pthread_t*)malloc(sizeof(pthread_t) * iThreadNum);
87     for (int i = 0; i < iThreadNum; ++i)
88     {
89         pthread_create(&pTid[i], NULL, func, NULL);
90     }
91 
92     for (int i = 0; i < iThreadNum; ++i)
93     {
94         pthread_join(pTid[i], NULL);
95     }
96 
97     return 0;
98 }
tcpCli
 1 /*
 2  * gcc -o tcpSvr ./tcpSvr.c
 3  */
 4 #include <unistd.h>
 5 #include <sys/socket.h>
 6 #include <sys/types.h>
 7 #include <arpa/inet.h>
 8 #include <netinet/in.h>
 9 #include <stdio.h>
10 #include <string.h>
11 #include <errno.h>
12 #include <pthread.h>
13 
14 const int PORT = 7766;
15 
16 int main(int argc, char **argv)
17 {
18     int fd = socket(AF_INET, SOCK_STREAM, 0);
19     if (fd == -1)
20     {
21         perror("socket");
22         return errno;
23     }
24 
25     printf("Port %d\n", PORT);
26     struct sockaddr_in addr;
27     memset(&addr, 0, sizeof(addr));
28     addr.sin_family = AF_INET;
29     addr.sin_addr.s_addr = INADDR_ANY;
30     addr.sin_port = htons(PORT);
31 
32     if (-1 == bind(fd, (struct sockaddr*)&addr, sizeof(addr)))
33     {
34         perror("bind");
35         return errno;
36     }
37 
38     if (-1 == listen(fd, 5))
39     {
40         perror("listen");
41         return errno;
42     }
43 
44     struct sockaddr_in cli_addr;
45     socklen_t cli_addr_len = sizeof(cli_addr);
46     sleep(1000000);
47 
48     int client = accept(fd, (struct sockaddr*)&cli_addr, &cli_addr_len);
49     if (client < 0)
50     {
51         perror("accept");
52         return errno;
53     }
54 
55     char buf[1500];
56     int iCount = 0;
57     int iZero = 0;
58     while(1)
59     {
60         memset(buf, 0, 1024);
61         int iRet = recv(client, buf, 1500, 0);
62         printf("Recv package count[%d]\t", iCount);
63         printf("recv content [%s]\n", buf);
64         iRet = send(client, buf, iRet, 0);
65 
66         iRet = iRet/iZero;
67     }
68     close(fd);
69 
70     return 0;
71 }
tcpSvr

实验结果:

执行:tcpCli 15 的结果

这是使用grep过滤了tcpSvr的显示结果:

这是使用grep过滤了tcpCli的显示结果:

这是过了一段时间后,tcpCli输出的结果:

TCP状态转移图:

然后根据这些结果以及TCP状态转移图,可以得出如下结论:

在server端处在ESTABLISHED状态的有6个连接,有4个处在SYN_RECV状态(这四个连接过一段时间会关闭)
但是在client端,15个连接全部处在ESTABLISHED状态,并且此次修改了tcpCli的代码,在connect之后打印“Connect Succ”显示15个连接都成功了。
安装TCP状态转移图,svr端收到TCK就应该转移到ESTABLISHED状态了,根据tcpdump抓包也看到回的ACK包,但是为什么netstat看不到这些连接、或者看到的SYN_RECV状态呢?
到底在tcp未处理连接请求达到backlog值之后,对于新到来的连接请求,tcp协议栈做什么处理呢?
 
根据tcpdump抓包数据分析:
无状态的端口对应的包:
svr端在不停的发送第二次握手的包(syn, ack=syn+1),这就意味着svr端未收到cli发的第三次握手的包(ack包),但是tcpdump抓包发现,每次svr发送过(syn,ack=syn+1)后,cli都回包了。这么分析只有一个原因,虽然tcp收到了ack包,但是没有接受、或者说抛弃了。于是tcp协议层就认为没有收到ack,于是发生重传。
 
SYN_RECV状态对应的包:
情况同上面类似,但是为什么它们的状态时SYN_RECV呢?
 
ESTABLISHED状态对应的包:
这就是正常的三次握手的包了。
 
2017-03-23 更新:
经过知乎上大牛的指点,关于服务端是SYN_RECV状态,而客户端是ESTABLISHED状态的疑问,有了答案,参考:tcp/dccp: drop SYN packets if accept queue is full

这段解释的意思就是,当服务端未处理连接队列满的时候,它就会丢掉client端发送的三次握手中的最后一个ACK包,这就会导致,client端以为自己已经建立连接了,但是实际在server端没有连接,同时也解释了tcpdump抓包看到的,为什么server一直不停的发送三次握手的第二次数据包(SYN+ACK)。

 

另外,关于这种类似的问题,就像知乎浅墨说的,策略在不停的改变,不要听信网上别人的说法,自己动手试一下就可以了。

posted @ 2017-03-17 21:06  冷冰若水  阅读(1357)  评论(0编辑  收藏  举报