深入理解Linux TCP backlog
当应用程序调用listen
系统调用让一个socket
进入LISTEN
状态时,需要指定一个参数:backlog
。这个参数经常被描述为,新连接队列的长度限制。
tcp-state-diagram.png
由于TCP
建立连接需要进行3次握手,一个新连接在到达ESTABLISHED
状态可以被accept
系统调用返回给应用程序前,必须经过一个中间状态SYN RECEIVED
(见上图)。这意味着,TCP/IP
协议栈在实现backlog
队列时,有两种不同的选择:
- 仅使用一个队列,队列规模由
listen
系统调用backlog
参数指定。当协议栈收到一个SYN
包时,响应SYN/ACK
包并且将连接加进该队列。当相应的ACK
响应包收到后,连接变为ESTABLISHED
状态,可以向应用程序返回。这意味着队列里的连接可以有两种不同的状态:SEND RECEIVED
和ESTABLISHED
。只有后一种连接才能被accept
系统调用返回给应用程序。 - 使用两个队列——
SYN
队列(待完成连接队列)和accept
队列(已完成连接队列)。状态为SYN RECEIVED
的连接进入SYN
队列,后续当状态变更为ESTABLISHED
时移到accept
队列(即收到3次握手中最后一个ACK
包)。顾名思义,accept
系统调用就只是简单地从accept
队列消费新连接。在这种情况下,listen
系统调用backlog
参数决定accept
队列的最大规模。
历史上,起源于BSD
的TCP
实现使用第一种方法。这个方案意味着,但backlog
限制达到,系统将停止对SYN
包响应SYN/ACK
包。通常,协议栈只是丢弃SYN
包(而不是回一个RST
包)以便客户端可以重试(而不是异常退出)。
TCP/IP详解 卷3
第14.5
节中有提到这一点。书中作者提到,BSD
实现虽然使用了两个独立的队列,但是行为跟使用一个队列并没什么区别。
在Linux
上,情况有所不同,情况listen
系统调用man
文档页:
The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length for completely established sockets waiting to be accepted, instead of the number of incomplete connection requests. The maximum length of the queue for incomplete sockets can be set using /proc/sys/net/ipv4/tcp_max_syn_backlog. When syncookies are enabled there is no logical maximum length and this setting is ignored.
意思是,
backlog
参数的行为在Linux
2.2之后有所改变。现在,它指定了等待accept
系统调用的已建立连接队列的长度,而不是待完成连接请求数。待完成连接队列长度由/proc/sys/net/ipv4/tcp_max_syn_backlog
指定;在syncookies
启用的情况下,逻辑上没有最大值限制,这个设置便被忽略。
也就是说,当前版本的Linux
实现了第二种方案,使用两个队列——一个SYN
队列,长度系统级别可设置以及一个accept
队列长度由应用程序指定。
现在,一个需要考虑的问题是在accept
队列已满而一个已完成新连接需要用SYN
队列移动到accept
队列(收到3次握手中最后一个ACK
包),这个实现方案是什么行为。这种情况下,由net/ipv4/tcp_minisocks.c
中tcp_check_req
函数处理:
child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);
if (child == NULL)
goto listen_overflow;
对于IPv4
,第一行代码实际上调用的是net/ipv4/tcp_ipv4.c
中的tcp_v4_syn_recv_sock
函数,代码如下:
if (sk_acceptq_is_full(sk))
goto exit_overflow;
可以看到,这里会检查accept
队列的长度。如果队列已满,跳到exit_overflow
标签执行一些清理工作、更新/proc/net/netstat
中的统计项ListenOverflows
和ListenDrops
,最后返回NULL
。这会触发tcp_check_req
函数跳到listen_overflow
标签执行代码。
listen_overflow:
if (!sysctl_tcp_abort_on_overflow) {
inet_rsk(req)->acked = 1;
return NULL;
}
很显然,除非/proc/sys/net/ipv4/tcp_abort_on_overflow
被设置为1
(这种情况下发送一个RST
包),实现什么都没做。
总结一下:Linux
内核协议栈在收到3次握手最后一个ACK
包,确认一个新连接已完成,而accept
队列已满的情况下,会忽略这个包。一开始您可能会对此感到奇怪——别忘了SYN RECEIVED
状态下有一个计时器实现:如果ACK
包没有收到(或者是我们讨论的忽略),协议栈会重发SYN/ACK
包(重试次数由/proc/sys/net/ipv4/tcp_synack_retries
决定)。
看以下抓包结果就非常明显——一个客户正尝试连接一个已经达到其最大backlog
的socket
:
0.000 127.0.0.1 -> 127.0.0.1 TCP 74 53302 > 9999 [SYN] Seq=0 Len=0
0.000 127.0.0.1 -> 127.0.0.1 TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
0.000 127.0.0.1 -> 127.0.0.1 TCP 66 53302 > 9999 [ACK] Seq=1 Ack=1 Len=0
0.000 127.0.0.1 -> 127.0.0.1 TCP 71 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
0.207 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
0.623 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
1.199 127.0.0.1 -> 127.0.0.1 TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
1.199 127.0.0.1 -> 127.0.0.1 TCP 66 [TCP Dup ACK 6#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
1.455 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
3.123 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
3.399 127.0.0.1 -> 127.0.0.1 TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
3.399 127.0.0.1 -> 127.0.0.1 TCP 66 [TCP Dup ACK 10#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
6.459 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
7.599 127.0.0.1 -> 127.0.0.1 TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
7.599 127.0.0.1 -> 127.0.0.1 TCP 66 [TCP Dup ACK 13#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
13.131 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
15.599 127.0.0.1 -> 127.0.0.1 TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
15.599 127.0.0.1 -> 127.0.0.1 TCP 66 [TCP Dup ACK 16#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
26.491 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
31.599 127.0.0.1 -> 127.0.0.1 TCP 74 9999 > 53302 [SYN, ACK] Seq=0 Ack=1 Len=0
31.599 127.0.0.1 -> 127.0.0.1 TCP 66 [TCP Dup ACK 19#1] 53302 > 9999 [ACK] Seq=6 Ack=1 Len=0
53.179 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
106.491 127.0.0.1 -> 127.0.0.1 TCP 71 [TCP Retransmission] 53302 > 9999 [PSH, ACK] Seq=1 Ack=1 Len=5
106.491 127.0.0.1 -> 127.0.0.1 TCP 54 9999 > 53302 [RST] Seq=1 Len=0
由于客户端的TCP
实现在收到多个SYN/ACK
包时,认为ACK
包已经丢失了并且重传它。如果在SYN/ACK
重试次数达到限制前,服务端应用从accept
队列接收连接,使得backlog
减少,那么协议栈会处理这些重传的ACK
包,将连接状态从SYN RECEIVED
变更到ESTABLISHED
并且将其加入accept
队列。否则,正如以上包跟踪所示,客户端会收到一个RST
包宣告连接失败。
在客户端看来,第一次收到SYN/ACK
包之后,连接就会进入ESTABLISHED
状态。如果这时客户端首先开始发送数据,那么数据也会被重传。好在TCP
有慢启动机制,在服务端还没进入ESTABLISHED
之前,客户端能发送的数据非常有限。
相反,如果客户端一开始就在等待服务端,而服务端backlog
没能减少,那么最后的结果是连接在客户端看来是ESTABLISHED
状态,但在服务端看来是CLOSED
状态。这也就是所谓的半开连接。
有一点还没讨论的是:man listen
中提到每次收到新SYN
包,内核往SYN
队列追加一个新连接(除非该队列已满)。事实并非如此,net/ipv4/tcp_ipv4.c
中tcp_v4_conn_request
函数负责处理SYN
包,请看以下代码:
if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) {
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
goto drop;
}
可以看到,在accept
队列已满的情况下,内核会强制限制SYN
包的接收速率。如果有大量SYN
包待处理,它们其中的一些会被丢弃。这样看来,就完全依靠客户端重传SYN
包了,这种行为跟BSD
实现一样。
下结论前,需要再研究以下Linux
这种实现方式跟BSD
相比有什么优势。Stevens
是这样说的:
在
accept
队列已满或者SYN
队列已满的情况下,backlog
会达到限制。第一种情况经常发生在服务器或者服务器进程非常繁忙的情况下,进程没法足够快地调用accept
系统调用从中取出已完成连接。后者是HTTP
服务器经常面临的问题,在服务端客户端往返时间非常长的时候(相对于连接到达速率),因为新SYN
包在往返时间内都会占据一个连接对象。
大多数情况下
accept
队列都是空的,因为一旦有一个新连接进入队列,阻塞等待的accept
系统调用将返回,然后连接从队列中取出。
Stevens
建议的解决方案是简单地调大backlog
。但有个问题是,应用程序在调优backlog
参数时,不仅需要考虑自身对新连接的处理逻辑,还需要考虑网络状况,包括往返时间等。Linux实现实际上分成两部分:应用程序只负责调解backlog
参数,确保accept
调用足够快以免accept
队列被塞满;系统管理员则根据网络状况调节/proc/sys/net/ipv4/tcp_max_syn_backlog
,各司其职。
本文译自:How TCP backlog works in Linux。
深入理解Linux TCP backlog的更多相关文章
- 理解 Linux backlog/somaxconn 内核参数
https://jaminzhang.github.io/linux/understand-Linux-backlog-and-somaxconn-kernel-arguments/ 各参数的含义:h ...
- 【翻译】TCP backlog在Linux中的工作原理
原文How TCP backlog works in Linux水平有限,难免有错,欢迎指出!以下为翻译: 当应用程序通过系统调用listen将一个套接字(socket)置为LISTEN状态时,需要为 ...
- Tomcat 调优之从 Linux 内核源码层面看 Tcp backlog
前两天看到一群里在讨论 Tomcat 参数调优,看到不止一个人说通过 accept-count 来配置线程池大小,我笑了笑,看来其实很多人并不太了解我们用的最多的 WebServer Tomcat,这 ...
- [转]linux tcp/ip调优
LINUX tcp/ip性能调优 On 2011年03月15日, in linux, tips, by netoearth 在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接 ...
- 深入理解linux网络技术内幕读书笔记(三)--用户空间与内核的接口
Table of Contents 1 概论 1.1 procfs (/proc 文件系统) 1.1.1 编程接口 1.2 sysctl (/proc/sys目录) 1.2.1 编程接口 1.3 sy ...
- 理解 Linux 配置文件分类和使用
理解 Linux 配置文件分类和使用 本文说明了 Linux 系统的配置文件,在多用户.多任务环境中,配置文件控制用户权限.系统应用程序.守护进程.服务和其它管理任务.这些任务包括管理用户帐号.分配磁 ...
- linux tcp调优
Linux TCP Performance Tuning News Linux Performance Tuning Recommended Books Recommended Links Linux ...
- WARNING: The TCP backlog setting of 511.解决
redis启动警告问题:WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/so ...
- [转]理解Linux的性能
来源:http://www.linuxfly.org/post/114/ [转]理解Linux的性能 项目中常遇到需要对目前运行的系统进行效率分析,或碰到客户咨询如何优化系统的效率问题.更 ...
随机推荐
- Web渗透-SQLmap
Web渗透-SQLmap 一.sqlmap简介 1.1 sqlmap 参数解析 二.sqlmap自动化注入 2.4 提权操作 示例步骤: 1.获得当前数据库 2.获得数据库表 3.获得表的字段 4.获 ...
- Java面试(解答题二)
1.一个用户具有多个角色,请查询出该表中具有该用户的所有角色的其他用户.备注:用户表:tb,角色字段为role,主键为id.请写出sql语句. 解答: 2.概述MVC体系结构 解答: MVC包括三类对 ...
- 深信服上网行为管理配置跨三层MAC识别
1.在认证高级选项里点击新增 如果PC的IP和MAC存在于多个三层交换机,则需新增多个. 点击上图"查看服务器信息"测试能否从交换机获取PC的IP和MAC,有返回结果则能正常获取, ...
- php之__DIR__,__FILE__,getcwd()的区别。
__DIR__ 在哪个脚本文件里面出现,就显示当前脚本的目录,不包含文件名.假如目录A下的1.php包含了这个魔术常量,这个文件被目录B下的2.php调用了.那么__DIR__返回的值是多少呢?返回的 ...
- mysql高级day2
Mysql高级-day02 1. Mysql的体系结构概览 整个MySQL Server由以下组成 Connection Pool : 连接池组件 Management Services & ...
- Codeforces Round #653 (Div. 3)
比赛链接:https://codeforces.com/contest/1374 A. Required Remainder 题意 给出 $x, y, n$,找到最大的整数 $0 \le k \le ...
- js面向对象封装级联下拉菜单列表
本实例开发的级联下拉菜单是根据已有json数据创建的DOM元素.点击文本框后,显示一级菜单.如果菜单中包含子菜单,菜单右侧会有指示箭头.点击菜单之后,会再显示下一级菜单,以此类推.当菜单下无子菜单时, ...
- 正则指引 pdf 高清版
链接:https://pan.baidu.com/s/1Xeuma4toE_L-MxROvTGBxw 提取码:nqyj
- Docker配置文件deamon.json详解
vim /etc/docker/daemon.json { "authorization-plugins": [], "data-root": "&q ...
- Python——控制鼠标键盘
一.安装包 pip install pynput 二.引用包 from pynput import mouse,keyboard 三.控制鼠标 from pynput.mouse import But ...