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

news2024/11/25 6:46:20

首先简单介绍下tcp的全连接队列(accept queue)和半连接队列(syn queue),
1.客户端发起syn请求时,服务端收到该请求,将其放入到syn queue,然后回复ack+syn给客户端。
2.客户端收到ack+syn,再发送ack给服务端。
3. 服务端从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的内容如下:

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状态了。

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

#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;
}

客户端的代码如下:

#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);                                         //关闭连接
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1580550.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2_8.Linux系统引导过程及引导修复

# 1.磁盘引导 # mbr主引导记录0磁道1扇区446 作用&#xff1a; 记录grub2引导文件的位置 当mbr数据丢失系统会因为找不到启动分区而停止启动 问题模拟方式: 系统磁盘/dev/sda dd if/dev/zero of/dev/vda bs446 count1 ##清空系统/dev/sda上的mbr数据 恢复方式&#xff1a; &…

Java多线程实战-从零手搓一个简易线程池(四)线程池生命周期状态流转实现

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️本系列源码仓库&#xff1a;多线程并发编程学习的多个代码片段(github) &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正…

【IC前端虚拟项目】spyglass lint环境组织与lint清理

【IC前端虚拟项目】数据搬运指令处理模块前端实现虚拟项目说明-CSDN博客 和上个虚拟项目的lint清理环节一样&#xff0c;关于spyglass的lint清理功能与流程还是大家通过各种资料去学习下就好啦。和之前不同的事&#xff0c;这次的虚拟项目里我把流程封装为Makefile&#xff0c;…

C. MEX Game 1

本题如果我们去模拟这个算法的话会很麻烦&#xff0c;也会TLE&#xff0c;首先我们想 1&#xff0c;对于alice来说&#xff0c;先取小的&#xff0c;对于bob来说先删除alic想取的下一个小的 2&#xff0c;那如果这个数多于两个&#xff0c;那也就是说&#xff0c;alice肯定能…

Linux操作系统安装注意事项(新手简易版)

Linux操作系统安装注意事项&#xff08;新手简易版&#xff09; 目录&#xff1a; 1、字符集安装 2、磁盘分区 3、关闭KDUMP防火墙 4、时区选择 注&#xff1a;事例截图是centos8的安装&#xff0c;其他版本是一样的 1、字符集安装 ecology运行需要用到GBK和UTF8字符…

LeetCode-84. 柱状图中最大的矩形【栈 数组 单调栈】

LeetCode-84. 柱状图中最大的矩形【栈 数组 单调栈】 题目描述&#xff1a;解题思路一&#xff1a;单调栈解题思路二&#xff1a;解题思路三&#xff1a; 题目描述&#xff1a; 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且…

anaconda命令行创建虚拟环境并为其安装jupyter notebook同时指定jupyter notebook保存位置

查看有哪些虚拟环境&#xff08;一个环境一个版本的python或者其他库&#xff09; winr快捷键 输入cmd conda env list应该是进入conda的安装路径&#xff0c;但是我们已经添加环境变量 可以看到只有base默认的环境 我们现在新建虚拟环境 python版本为你需要的 conda create -…

Harmony鸿蒙南向驱动开发-GPIO

GPIO&#xff08;General-purpose input/output&#xff09;即通用型输入输出。通常&#xff0c;GPIO控制器通过分组的方式管理所有GPIO管脚&#xff0c;每组GPIO有一个或多个寄存器与之关联&#xff0c;通过读写寄存器完成对GPIO管脚的操作。 基本概念 GPIO又俗称为I/O口&am…

web安全学习笔记(8)

记一下第十二节课的内容。 一、PHP文件包含的四种方式 Include和Include_once 操作系统会读取包含的文件的内容&#xff0c;并将它插入主文件中&#xff0c;include方式的文件包含会在包含失败的情况下输出警告信息&#xff0c;而include_once方式会检查包含的文件是否已经被…

【数据结构与算法篇】单链表及相关OJ算法题

【数据结构与算法篇】单链表及相关OJ算法题 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;数据结构与算法&#x1f345; &#x1f33c;文章目录&#x1f33c; 1. 单链表的实现(近300行实现代码) 1.1 SList.h 头文件的声明 1.2 SLi…

指针 运算偏移

思维导图&#xff1a; 题目&#xff1a; 1.变量的指针&#xff0c;其含义是指该变量的 B 。 A&#xff09;值 B&#xff09;地址 C&#xff09;名 D&#xff09;一个标志 2.已有定义int k2;int *ptr1,*ptr2;且ptr1和ptr2均…

每日OJ题_两个数组dp⑥_力扣97. 交错字符串

目录 力扣97. 交错字符串 解析代码 力扣97. 交错字符串 97. 交错字符串 难度 中等 给定三个字符串 s1、s2、s3&#xff0c;请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。 两个字符串 s 和 t 交错 的定义与过程如下&#xff0c;其中每个字符串都会被分割成若干 非空 子…

YOLOv5标签值含义根据标签将检测框色块替换(马赛克)

以一个检测人脸的图片为例&#xff1a; 检测后生成的标签txt如下&#xff0c; 此时&#xff0c;如何根据标签值将检测到的人脸同色块替换呢&#xff1f; 关键是获取检测框的左上角坐标和右下角坐标。 img Image.open(D:/PythonWokspace/JINX/datasets_transform/dataset/im…

【grpc】三、grpc入门,内置日志打印

一、开启条件 通过查看 grpc 的内置文件可以看到&#xff0c;日志打印是由环境变量控制的&#xff1a; export GRPC_GO_LOG_VERBOSITY_LEVEL99 # 日志详细程度&#xff0c;这里99为最高 export GRPC_GO_LOG_SEVERITY_LEVELinfo # 日志等级&#xff0c;这里为info如果是用Gol…

接雨水(双指针)

11. 盛最多水的容器 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 样例输入 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,…

Vue文档

Vue是什么&#xff1f;为什么要学习他 Vue是什么&#xff1f; Vue是前端优秀框架&#xff0c; 是一套用于构建用户界面的渐进式框架 为什么要学习Vue Vue是目前前端最火的框架之一Vue是目前企业技术栈中要求的知识点Vue可以提升开发体验Vue学习难度较低… Vue开发前的准备 安…

SpringMVC数据接收(全面/详细注释)

SpringMVC涉及组件&#xff1a; DispatcherServlet : SpringMVC提供&#xff0c;我们需要使用web.xml配置使其生效&#xff0c;它是整个流程处理的核心&#xff0c;所有请求都经过它的处理和分发&#xff01;[ CEO ]HandlerMapping : SpringMVC提供&#xff0c;我们需要进行…

MySQL8.3.0 主从复制方案(master/slave)

一 、什么是MySQL主从 MySQL主从&#xff08;Master-Slave&#xff09;复制是一种数据复制机制&#xff0c;用于将一个MySQL数据库服务器&#xff08;主服务器&#xff09;的数据复制到其他一个或多个MySQL数据库服务器&#xff08;从服务器&#xff09;。这种复制机制可以提供…

若依框架学习——分页查询列表

条件查询【多条件】列表展示【分页】SaCheckPermissionTableName TableId NotBlank Page分页 字典&#xff1a; 响应数据封装类

ModuleNotFoundError: No module named ‘ultralytics.utils‘

项目场景he 问题描述 提示&#xff1a;这里简述项目相关背景&#xff1a; model YOLO(modelr./yolov8m-cls.pt) 加载预训练模型时报错。 ModuleNotFoundError: No module named ultralytics.utils warning: bug: 原因分析&#xff1a; 很可能是提前下载的预训练模型出了…