listen函数backlog参数的一点探讨

2023-10-11 23:30

本文主要是介绍listen函数backlog参数的一点探讨,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言:

        今年上半年的时候, 因为自己工作的失误, 导致程序的TCP三次握手非常缓慢, 存在大量syn_recv状态连接. 查了很多资料(尤其是listen的相关资料)都无法完美解决问题, 虽然调大了backlog参数, 但是连接数达到一定值(backlog)后, 三次握手同样非常缓慢. 后面才发现是自己代码的原因, 具体来讲, 就是我那部分代码阻塞了进程, 相当于sleep了下(虽然不是真的sleep). 后面我把这部分代码给删除了, 程序恢复了正常.

        最近一段时间, 又想到了这个问题, 所以就想写个测试程序来模拟当时的场景, 顺便对当时搜的资料做个总结. 

         本文基于centos7环境, linux3.10内核. 好吧, 那就开始吧.

开始:

        这里有几个名词/参数需要了解下, 本文会遇到.

        全连接队列: 完成三次握手处于ESTABLISHED状态.

        未全连接队列: 此处称之为半连接队列. 服务端收到了SYN后半连接队列+1然后再回复SYN+ACK. 回复SYN + ACK后此时处于SYN_RECV状态.

        变量backlog: 函数listen的第二个参数.

        变量somaxconn: /proc/sys/net/core/somaxconn.

        变量tcp_max_syn_backlog: /proc/sys/net/ipv4/tcp_max_syn_backlog.

        变量syncookies: /proc/sys/net/ipv4/tcp_syncookies.

        变量tcp_synack_retries: net.ipv4.tcp_synack_retries, 含义是未激活的TCP连接发送SYN/ACK段的重试次数. 可以写在/etc/sysctl.conf内, 也可以通过代码设置.

        可以通过man 2 listen命令可以查看如下解释:

         大概意思是: backlog指定的是等待被accept的全连接socket队列长度, 代替了未全连接请求的数量. 未全连接socket队列的最大长度可以通过tcp_max_syn_backlog设置, 当设置了syncookies, 则不是逻辑上的最大长度, 这个设置可以忽略.

        还有一个系统默认参数somaxconn, 两者取最小值为全连接队列长度.

        读完这个解释后, 心里冒出几个问题:

        1, 半连接队列长度是多大.

        2, 全连接队列长度是多大.

        3, 半连接队列长度和全连接队列长度何时加减.

        4, 如何测试.

        关于第1, 2个问题, 详细解答可以查看下面的链接, 这里只做简答答疑.

TCP 的backlog详解及半连接队列和全连接队列_Blue summer的博客-CSDN博客_tcp 半连接

第1部分, 半连接队列长度:        

         这个函数的功能是判断半连接队列是否满了. 第一个变量为当前半连接队列的实际大小, 第二个变量为2^n(实际分析下来, 2^n就是半连接队的设置大小)中的指数n. 现在只需要知道max_qlen_log这个数值如何得来的就可以了. 看下图.

        传入的参数nr_table_entries的实际值为min(backlog, somaxconn), 那么:

        第46行: x = min(backlog, somaxconn, tcp_max_syn_backlog)

        第47行: y = max(x, 8)

        第48行: nr_table_entries的最终值为 (y + 1)最接近的2^n, 此为半连接队列大小, max_qlen_log的值为n.

第2部分, 全连接队列长度:

        这个函数的功能是判断全连接队列是否满了. 第一个变量指的是当前全连接队列的实际大小, 第二个变量是全连接队列的设置大小, 如此我们只需知道第二个变量是如何得来的就可以了.

        在这里我们需要查看两个函数, 第一个函数为listen的系统调用SYSCALL_DEFINE2(listen, int, fd, int, backlog). 第二个函数为其调用的具体函数int inet_listen(struct socket *sock, int backlog). 两个函数里分别有如下实现:

         可以看到, 函数1的实现就是取两者最小值, 函数2的实现就跟上述判断全连接队列是否满了函数的第二个变量匹配起来, 所以全连接队列的最终大小是: min(backlog, somaxconn)

第3部分, 队列加减操作: 

3.1 半连接队列+1:

        在服务端收到第一次握手的SYN时半连接队列+1, 具体调用如下.

 3.2 半连接队列-1同时全连接队列+1:

        在服务端收到第三次握手客户端发送的ACK时半连接队列会-1, 同时全连接队列会+1. 具体调用如下.

 3.3 全连接队列-1:

        在服务端调用accept时全连接队列-1. 具体调用如下.

第4部分, 如何测试: 

        主要测试的内容是, 全连接队列已满时的各种信息. 所以, 测试代码实现的是不完整的非常规socket: 服务端只listen而没有accept调用, 客户端只connect而不读写. 测试代码见末尾.

4.1 条件1:

backlogsomaxconntcp_max_syn_backlogsyncookiestcp_synack_retries
1616161(开启)127

        根据第1部分和第2部分, 我们可以知道半连接队列设置大小为32, 全连接队列设置大小为16. 下面通过测试也简单验证下.

        4.1.1 测试1:

        客户端启动10个tcp连接. 通过ss -ln命令分析, 结果如下图:

        可以看到, 全连接队列设置大小为16, 结果跟预测匹配; 全连接队列当前大小为10, 跟10个tcp连接匹配. 

        4.1.2 测试2:

        客户端启动25个tcp连接. 通过ss和netstat命令分析, 结果如下图:

        可以看到, 全连接队列当前大小为17, ESTABLISHED状态的个数也为17,结果互相匹配; 半连接状态的个数为8, 总共25个tcp连接, 结果匹配. 

        4.1.3 测试3:

        客户端启动49个tcp连接. 通过netstat命令分析, 结果如下图:

        可以看到, 半连接状态的最大值为32, 那么半连接队列当前大小的最大值为32. 

        4.1.4 测试4:

        客户端启动50个tcp连接. 通过netstat命令分析, 并用demsg查看内核日志, 结果如下图:

        可以发现, 半连接状态的最大值为32, 那么半连接队列当前大小最大值为32, 再通过和测试3比对, 可以知道半连接队列设置大小为32, 跟预测结果一致. 如果半连接队列已满时还有客户端的SYN请求, 那么内核将会出现syn flooding日志. 内核实现如下:

4.2 条件2:

backlogsomaxconntcp_max_syn_backlogsyncookiestcp_synack_retries
1616160(关闭)127

       4.2.1 测试1:

        客户端启动30个tcp连接, 用demsg命令查看内核日志, 并无明显日志.

       4.2.2 测试2:

        客户端启动31个tcp连接, 用dmesg命令查看内核日志:

        说明, 当关闭syncookies时, 半连接队列未满就已经开始丢弃SYN请求了. 内核实现如下:

结尾:

        其实我还是有些疑问的.

        1, 为什么全连接队列设置大小为16, 而最终ESTABLISHED的个数为17, 多个数值尝试都为+1的关系.

        这个疑问通过跟别人交流, 已经有了答案. 如下:

        切换到全连接队列的处理逻辑是这样子的: 先判断全连接队列是否已满, 再执行++操作; 如果backlog为16, 第16个ACK(第三次握手)到来时, 全连接队列实际大小为15, 未满, 则++, 此时才为16; 第17个ACK到来时, 实际大小为16, 而判断逻辑是>, 未满, 又++, 此时为17, 后续的就满了, 符合+1的逻辑关系.

        2, 4.1.3 测试3, 49个tcp连接, 此时并没有引起syn flooding, 为何半连接队列当前大小不能维持在32个, 因为我设置的重试次数为127次, 并没有达到重试的次数就开始降低了.

        3, 4.2.2 测试2, 根据代码, 半连接数达到13个可以使第二个条件成立(16 – 13 < 4), 那么理论上总连接数30(13 + 17全连接)个就能触发drop条件, 而最终的结果是启动31个连接数才能触发.

        本人能力有限, 文中或许有些错误, 如果有同学发现, 敬请指教. 或者有同学知道我的疑问, 也希望不吝赐教. 但是通过这次测试, 我对backlog参数有一个比较深的理解. 也期望自己能在余下的时间完善自己, 找到这些问题的答案.

代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>typedef struct config
{int mode;char *addr;unsigned short int port;int backlog;int conn_num;struct sockaddr_in addr_in;socklen_t  addr_in_len;
}config_t;static int tcp_socket(const config_t *conf)
{int sfd = socket(AF_INET, SOCK_STREAM, 0);if (sfd == -1){printf("socket failed, err msg: %s\n", strerror(errno));return -1;}int val1 = 1;if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR | (conf->mode == 0 ? SO_REUSEPORT : 0), (void *)&val1, sizeof(val1)) == -1){printf("setsockopt failed, err msg: %s\n", strerror(errno));goto FAILED;               }if (conf->mode == 0){if (bind(sfd, (struct sockaddr*)&conf->addr_in, conf->addr_in_len) == -1){printf("bind failed, err msg: %s\n", strerror(errno));goto FAILED;               }if (listen(sfd, conf->backlog)){printf("bind failed, err msg: %s\n", strerror(errno));goto FAILED;                              }                       }else{if (connect(sfd, (struct sockaddr*)&conf->addr_in, conf->addr_in_len)){printf("connect failed, err msg: %s\n", strerror(errno));goto FAILED;                        }                }return sfd;FAILED:close(sfd);return -1;
}static void sig_call(int sig)
{printf("capture sig: %d\n", sig);
}static void server(config_t *conf)
{int sock_fd = tcp_socket(conf);if (sock_fd == -1){return;}// 不acceptsleep(3600);
}static void client(config_t *conf)
{const int SIZE = conf->conn_num;int fds[SIZE], i = 0;for(; i < SIZE; ++i){if ((fds[i] = tcp_socket(conf)) == -1){return;}if (i > conf->backlog){sleep(1);}}sleep(3600);
}int main(int argc, char *argv[])
{signal(SIGPIPE, sig_call);config_t conf = {.mode = atoi(argv[1]),          // 0-服务端, 非0-客户端.addr = argv[2],                // 地址.port = atoi(argv[3]),          // 端口.backlog = atoi(argv[4]),       // 服务端listen的第二个参数.conn_num = atoi(argv[5]),      // 客户端启动TCP连接数.addr_in.sin_family = AF_INET,.addr_in.sin_addr.s_addr = inet_addr(argv[2]),.addr_in.sin_port = htons(atoi(argv[3])),.addr_in_len = sizeof(struct sockaddr_in),};if (conf.mode == 0){server(&conf);}else{client(&conf);}return 0;
}

这篇关于listen函数backlog参数的一点探讨的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/191608

相关文章

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

如何在页面调用utility bar并传递参数至lwc组件

1.在app的utility item中添加lwc组件: 2.调用utility bar api的方式有两种: 方法一,通过lwc调用: import {LightningElement,api ,wire } from 'lwc';import { publish, MessageContext } from 'lightning/messageService';import Ca

4B参数秒杀GPT-3.5:MiniCPM 3.0惊艳登场!

​ 面壁智能 在 AI 的世界里,总有那么几个时刻让人惊叹不已。面壁智能推出的 MiniCPM 3.0,这个仅有4B参数的"小钢炮",正在以惊人的实力挑战着 GPT-3.5 这个曾经的AI巨人。 MiniCPM 3.0 MiniCPM 3.0 MiniCPM 3.0 目前的主要功能有: 长上下文功能:原生支持 32k 上下文长度,性能完美。我们引入了

C++操作符重载实例(独立函数)

C++操作符重载实例,我们把坐标值CVector的加法进行重载,计算c3=c1+c2时,也就是计算x3=x1+x2,y3=y1+y2,今天我们以独立函数的方式重载操作符+(加号),以下是C++代码: c1802.cpp源代码: D:\YcjWork\CppTour>vim c1802.cpp #include <iostream>using namespace std;/*** 以独立函数

AI(文生语音)-TTS 技术线路探索学习:从拼接式参数化方法到Tacotron端到端输出

AI(文生语音)-TTS 技术线路探索学习:从拼接式参数化方法到Tacotron端到端输出 在数字化时代,文本到语音(Text-to-Speech, TTS)技术已成为人机交互的关键桥梁,无论是为视障人士提供辅助阅读,还是为智能助手注入声音的灵魂,TTS 技术都扮演着至关重要的角色。从最初的拼接式方法到参数化技术,再到现今的深度学习解决方案,TTS 技术经历了一段长足的进步。这篇文章将带您穿越时

函数式编程思想

我们经常会用到各种各样的编程思想,例如面向过程、面向对象。不过笔者在该博客简单介绍一下函数式编程思想. 如果对函数式编程思想进行概括,就是f(x) = na(x) , y=uf(x)…至于其他的编程思想,可能是y=a(x)+b(x)+c(x)…,也有可能是y=f(x)=f(x)/a + f(x)/b+f(x)/c… 面向过程的指令式编程 面向过程,简单理解就是y=a(x)+b(x)+c(x)

如何确定 Go 语言中 HTTP 连接池的最佳参数?

确定 Go 语言中 HTTP 连接池的最佳参数可以通过以下几种方式: 一、分析应用场景和需求 并发请求量: 确定应用程序在特定时间段内可能同时发起的 HTTP 请求数量。如果并发请求量很高,需要设置较大的连接池参数以满足需求。例如,对于一个高并发的 Web 服务,可能同时有数百个请求在处理,此时需要较大的连接池大小。可以通过压力测试工具模拟高并发场景,观察系统在不同并发请求下的性能表现,从而

利用matlab bar函数绘制较为复杂的柱状图,并在图中进行适当标注

示例代码和结果如下:小疑问:如何自动选择合适的坐标位置对柱状图的数值大小进行标注?😂 clear; close all;x = 1:3;aa=[28.6321521955954 26.2453660695847 21.69102348512086.93747104431360 6.25442246899816 3.342835958564245.51365061796319 4.87