tcp的全连接队列和半连接队列满时,客户端再connect发生的情况

首先简单介绍下tcp的全连接队列(accept queue)和半连接队列(syn queue),

1.客户端发起syn请求时,服务端收到该请求,将其放入到syn queue,然后回复ack+syn给客户端。

2.客户端收到ack+syn,再发送ack给服务端。

  1. 服务端从syn queue中查找对应记录,完成连接建立,并且将此记录从半连接队列中删除,然后将建 立的连接塞入到accept queue。

上面就是三次握手建立连接的过程,连接建立后,服务端调用accept从accept queue中取出一条连接。

如果服务端发现accept queue满了后,无法塞入,会出现什么情况呢。

为模拟这种情况,可以先将服务端的accept函数注释掉,这样accept queue中的连接无法被消费,从而很快就满了,而且为了快速达到效果,本人在sysctl.conf中,设置net.core.somaxconn=2,即全连接队列的长度为2.

现在100个客户端程序同时执行,如下所示,可以看到服务端的ESTABLISHED的数量是3,不是2。

为何ESTABLISHED的数量是3,不是2,即刚好比全连接队列的长度大1,这里本人给出自己的看法,即在最后一个连接(即第3个)建立后,再往accept队列塞的时候,发现队列已经满了,所以就不塞了,但是连接已经建立好了。所以数目是队列的长度+1。

可以看出,全连接队列满了后,会出现SYN_RECV的状态,此状态的出现其实标志着全连接队列已经满了。

再过一段时间,可以发现SYN_RECV的状态消失,如下所示:

本人的sysctl.conf的内容如下:

bash 复制代码
net.ipv4.tcp_max_syn_backlog=2
net.ipv4.tcp_abort_on_overflow=0
net.core.somaxconn=2
fs.suid_dumpable=1
vm.swappiness=100
net.ipv4.tcp_synack_retries = 8

其中tcp_max_syn_backlog是半连接队列大小,也是2,但是可以看到上面的SYN_RECV的状态数目不是2,也不是3。这块tcp确实设计的很复杂,硬去理解比较头疼,暂时可以不用去管。

上面SYN_RECV和ESTABLISHED的数量是11,剩余的80多个连接,最终由于connect超时而失败。

下面解释下为何SYN_RECV存在一段时间后,最终都消失了。

首先全连接队列满了后,客户端在发起一次SYN后,客户端将此连接放入syn queue;然后发送ack+syn给客户端,客户端ack给服务端。

此时服务端发现全连接队列已经满了,然后就不再认为客户端的ack有效,其重新发送ack+syn给客户端,并且此次发送的ack+syn的序列号(seq)跟之前一样。

之后客户端收到ack+syn,再发送ack给服务端,如此反复若干次,服务端ack+syn重复的次数跟net.ipv4.tcp_synack_retries有关,该次数默认是5。

服务端这样做的目的在于缓冲,避免一下子上来一堆连接,导致连接队列满,后面再连接直接失败。

如上图所示,10.0.0.60是客户端,64是服务端,注意Time这一列,看上面的时间,6,7,9,13,22,38,70,134。

可以很快算出间隔为1,2,4,8,16,32,64。

即每次客户端ack后,服务端若判断客户端ack无效,则其再次发送ack+syn的间隔时间要在上次的基础上翻倍。

最后一次,服务端发现全连接队列依然是满的,则认为再给客户端发ack+syn失去了意义,所以将此半连接也删掉,从而看不到SYN_RECV状态了。

下面给出客户端和服务端程序的代码,

服务端的代码如下:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>

#define PORT 8888                                    //侦听端口地址:8888
#define BACKLOG 20                                    //侦听队列长度:2

extern void process_conn_server(int s);              //服务器对客户端的处理:读取数据并发送响应字符

int main(int argc,char *argv[])
{
        int ss = 0;                                      //ss = server socket = 服务器socket描述符
    int cs = 0;                                      //cs = client socket = 客户端socket描述符
    struct sockaddr_in server_addr;                  //服务器地址结构
    struct sockaddr_in client_addr;                  //客户端地址结构
    int ret = 0;                                     //返回值
    pid_t pid;
        //进程ID
        char buffer[1024];
        ssize_t size = 0;

    /**
     *  Step 2 : 建立套接字
    */
    ss = socket(AF_INET,SOCK_STREAM,0);              //创建一个AF_INET族的流类型socket
    if(ss < 0)                                       //检查是否正常创建socket
    {
        perror("socket error\n");
        exit(EXIT_FAILURE);
    }
   
    /**
     *  Step 3 : 设置服务器地址
     *  Note:
     *      htonl():将主机数转换成无符号长整型的网络字节顺序
     *      htons():将整型变量从主机字节顺序转变成网络字节顺序
    */
    bzero(&server_addr,sizeof(server_addr));          //清零
    server_addr.sin_family = AF_INET;                 //设置地址族为AF_INET
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //本地地址
        server_addr.sin_port = htons(PORT);               //设置端口号

    /**
     *  Step 4 : 绑定地址结构到套接字描述符
    */
    ret = bind(ss,(struct sockaddr*)&server_addr,sizeof(server_addr));
    if (ret < 0)                                      //出错
    {
        perror("bind error\n");
        exit(EXIT_FAILURE);
    }

    /**
     *  Step 5 : 设置侦听,侦听队列长度为2,可同时处理两个客户端连接请求
    */
    ret = listen(ss,BACKLOG);
    if (ret < 0)                                      //出错
    {
        perror("bind error\n");
        exit(EXIT_FAILURE);
    }

    /**
     *  Step 6 : 主循环过程
    */
    sleep(6000);
    for(;;)
    {
        /* 接收客户端连接 */
        int addrlen = sizeof(struct sockaddr);
        cs = accept(ss,(struct sockaddr*)&client_addr,&addrlen);
        if(cs < 0)                                    //出错
        {
            continue;                                 //结束本次循环
        }
        sleep(30);

/*
                for(;;)
                {
                        size = read(cs,buffer,1024);                 //从套接字中读取数据放到缓冲区buffer中

                        if(size == 0)                               //没有数据
                        {
                                return;
                        }

                        printf("buffer is %s\n", buffer);
                }
*/



    }
        return 0;
}

客户端的代码如下:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define PORT 8888                                     //端口地址:8888
#define SERVER_IP "10.0.0.64"

extern void process_conn_client(int s);

int main(int argc,char *argv[])
{
    int s = 0;                                        //socket描述符
    struct sockaddr_in server_addr;                   //服务器地址结构
    int ret = 0;                                      //返回值
    char buf[1024] = {0};
    int i = 0;
    strcpy(buf, "hello world");
    /**
     *  Step 2 : 建立套接字
     */
    s = socket(AF_INET,SOCK_STREAM,0);                //创建一个AF_INET族的流类型socket
    if(s < 0)                                         //检查是否正常创建socket
    {
        perror("socket error\n");
        exit(EXIT_FAILURE);
    }

    /**
     *  Step 3 : 设置服务器地址
     */
    memset(&server_addr, 0, sizeof(server_addr));          //清零
    server_addr.sin_family = AF_INET;                 //设置地址族为AF_INET
    inet_pton(AF_INET, SERVER_IP, &(server_addr.sin_addr));
    server_addr.sin_port = htons(PORT);               //设置端口号

    /**
     *  Step 4 : 将用户输入的字符串类型的IP地址转为整型
     */
    //inet_pton(AF_INET,argv[1],&server_addr.sin_addr);

    /**
     *  Step 5 : 连接服务器
     */
    ret = connect(s,(struct sockaddr*)&server_addr,sizeof(struct sockaddr));
    if(ret < 0)
    {
        perror("connect error\n");
        printf("errno is %d\n", errno);
        exit(EXIT_FAILURE);
    }
    printf("connect succeed\n");
    for(;;)
    {
        sleep(10000000);
        sprintf(buf, "hello world, %d", i);
        i++;
        write(s,buf,strlen(buf)+1);
    }
    close(s);                                         //关闭连接
}
相关推荐
Koi慢热28 分钟前
信息收集合集
网络·安全·web安全·网络安全
运维小文31 分钟前
服务器硬件介绍
运维·服务器·计算机网络·缓存·硬件架构
小周不摆烂37 分钟前
丹摩征文活动 | 丹摩智算平台:服务器虚拟化的璀璨明珠与实战秘籍
大数据·服务器
中云DDoS CC防护蔡蔡38 分钟前
为什么海外服务器IP会被封
服务器·经验分享
是安迪吖41 分钟前
nfs服务器
运维·服务器
鱼骨不是鱼翅43 分钟前
模拟回显服务器
运维·服务器
轩轩曲觞阁1 小时前
Linux网络——网络初识
linux·网络
摘星星ʕ•̫͡•ʔ1 小时前
计算机网络 第二章:物理层
网络·计算机网络
linnux领域1 小时前
使用ensp配置单臂路由、静态路由,实现PC互相通信。
网络
hgdlip1 小时前
本机ip地址和网络ip地址一样吗
网络·网络协议·tcp/ip·网络ip地址·本机ip地址