• 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
    ", iWBufSize);
    37 
    38     int iRBufSize;
    39     optLen = sizeof(iRBufSize);
    40     getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &iRBufSize, &optLen);
    41     printf("Read buffer size = %d
    ", 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]
    ", iRet);
    63             iCount++;
    64         }
    65         if (iCount % 100 == 0)
    66         {
    67             // printf("Send package count %d
    ", iCount);
    68             sleep(5);
    69         }
    70     }
    71     printf("Send package count %d
    ", 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
    ", argv[0]);
    81         return 0;
    82     }
    83 
    84     int iThreadNum = atoi(argv[1]);
    85     printf("ThreadNum [%d]
    ", 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
    ", 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]	", iCount);
    63         printf("recv content [%s]
    ", 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)。

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

  • 相关阅读:
    基于AjaxHelper的企业门户网站构架示例
    重读GoF
    来一点反射,再来一点Emit —— 极度简化Entity!
    Component/Service Oriented Software System Development Thinking
    重新诠释AOP
    With AOP, Component Oriented == Object Oriented
    没有ORM或代码生成数据就不能持久化了? 用范型技术代替代码生成!
    Dalvik Debug Monitor Service(DDMS)的使用
    Android中的布局 Layout
    堆排序 Heap Sort
  • 原文地址:https://www.cnblogs.com/lit10050528/p/6568357.html
Copyright © 2020-2023  润新知