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 }
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 }
实验结果:
执行: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)。
另外,关于这种类似的问题,就像知乎浅墨说的,策略在不停的改变,不要听信网上别人的说法,自己动手试一下就可以了。