实验环境: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 #include5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 12 #include 13 #include 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 size33 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 else61 {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 #include5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 12 #include 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状态的疑问,有了答案,参考:
这段解释的意思就是,当服务端未处理连接队列满的时候,它就会丢掉client端发送的三次握手中的最后一个ACK包,这就会导致,client端以为自己已经建立连接了,但是实际在server端没有连接,同时也解释了tcpdump抓包看到的,为什么server一直不停的发送三次握手的第二次数据包(SYN+ACK)。
另外,关于这种类似的问题,就像知乎浅墨说的,策略在不停的改变,不要听信网上别人的说法,自己动手试一下就可以了。