【翻译】TCP backlog在Linux中的工作原理
原文How TCP backlog works in Linux
水平有限,难免有错,欢迎指出!
以下为翻译:
当应用程序通过系统调用listen将一个套接字(socket)置为LISTEN状态时,需要为该套接字指定一个backlog参数,该参数通常被描述为用来限制进来的连接队列长度(queue of incoming connections)。
由于TCP协议的三次握手机制,一个进来的套接字连接在进入ESTABLISHED状态并且可以被accept调用返回给应用程序之前,会经历中间状态SYN RECEIVED(见上图)。这意味着TCP协议栈可以有两种方案来实现backlog队列:
- 只使用一个队列,队列大小由系统调用listen的backlog参数指定。服务端收到一个SYN数据包后将返回一个SYN/ACK数据包,同时将该连接放入到队列中。当服务端再次收到客户端返回的ACK时,该连接状态将变为ESTABLISHED,从而有资格被交给应用程序处理。
- 使用两个队列,一个SYN队列和一个accept队列。处于SYN RECEIVED状态的连接会被添加到SYN队列中,等到后续收到ACK变为ESTABLISHED状态后,该连接会被转移到accept队列中。顾名思义,在这种实现方式下,accept系统调用可以简单地实现为从accept队列中消费连接,此时listen调用的backlog参数决定的是accept队列的大小。
历史上,BCD派生的TCP实现采用第一种方案,这意味着当队列大小达到backlog最大值时,系统将不再发送用以响应SYN数据包的SYN/ACK数据包。通常,TCP实现将简单地丢弃收到的SYN数据包(而不是发送RST数据包)以便客户端重试。这也是W. Richard Stevens的经典教科书《TCP/IP详解 卷三》14.5节listen Backlog Queue中所描述的方案。
需要注意的是,W. Richard Stevens解释说BSD的实现实际上确实是使用两个单独的队列,但是它们表现为一个单个队列,其最大长度固定且由(但不是必须完全等于)backlog参数确定,即BSD逻辑上如方案1所述。
Linux上的情况有些不同,listen的man手册写到:
The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length forcompletely 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
TCP套接字上的backlog参数的行为随Linux 2.2而改变。 现在它指等待被接受(accept)的、完全建立的套接字的队列长度,而不是不完整的连接请求数。 不完整套接字队列的最大长度可以通过/proc/sys/net/ipv4/tcp_max_syn_backlog设置
这意味着当前Linux版本采用的是具有两个不同队列的方案二:一个SYN队列,大小由系统范围的设置指定;一个accept队列,大小由应用程序指定。 方案2一个有趣的问题是,如果当前accept队列已满,而一个连接需要从SYN队列中移到accept队列中,这个时候TCP实现将如何处理?这种情况由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数据包),否则TCP实现基本上不做任何事情!
总而言之,如果Linux中的(服务端)TCP实现接收到(客户端)三次握手的ACK数据包,并且accept队列已满,则(服务端)基本上将忽略该数据包。这种处理方式刚听起来可能有点奇怪,但是请记住,SYN RECEIVED状态有一个关联定时器:如果服务端没有收到ACK(或者像这里所说的被忽略),则TCP实现将重新发送 SYN/ACK数据包(重试次数由/proc/sys /net/ipv4/tcp_synack_retries指定,并使用指数退避算法)。
上述现象可以在以下数据包跟踪中看到,客户端尝试连接(并发送数据)到已达到其最大backlog的套接字:
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数据包丢失,从而重新发送ACK(参见上述跟踪中的TCP Dup ACK行)。
如果服务器端的应用程序在达到SYN/ACK最大重试次数之前减少了backlog(即从accept队列中消耗了一个条目),则TCP实现最终将会处理一个客户端重复发送的ACK,将连接状态从SYN RECEIVED转换到ESTABLISHED,并将连接添加到accept队列。否则的话,客户端最终会收到一个RST数据包(如上图所示)。
数据包跟踪同时也展示了上述行为另一个有趣的一面。从客户端的角度来说,TCP连接将在收到第一个SYN/ACK数据包之后变为ESTABLISHED状态。如果客户端向服务端发送数据(不等待来自服务端的数据),则该数据也会被重传。幸运的是,TCP的慢启动可以限制重传阶段发送的数据段个数。
另一方面,如果客户端一直在等待来自服务端的数据,而服务端的backlog一直没有降低,则最终的结果是客户端的连接状态是ESTABLISHED,而服务端的连接状态则是SYN_RCVD(注:原文说的是CLOSED状态,应该是不对的),也就是处于一个半连接的状态!
还有另一个方面我们目前没有讨论。listen的man手册引用表明,除非SYN队列已满,否则每个SYN数据包都将导致TCP连接被添加到SYN队列中,这种说法和实际情况有所出入,原因在于net/ipv4/tcp_ipv4.c中的tcp_v4_conn_request函数存在以下一段代码:
/* Accept backlog is full. If we have already queued enough
* of warm entries in syn queue, drop request. It is better than
* clogging syn queue with openreqs with exponentially increasing
* timeout.
*/
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提出了以下有趣的观点:
The backlog can be reached if the completed connection queue fills (i.e., the server process or the server host is so busy that the process cannot call accept fast enough to take the completed entries off the queue) or if the incomplete connection queue fills. The latter is the problem that HTTP servers face, when the round-trip time between the client and server is long, compared to the arrival rate of new connection requests, because a new SYN occupies an entry on this queue for one round-trip time. […]
The completed connection queue is almost always empty because when an entry is placed on this queue, the server’s call to accept returns, and the server takes the completed connection off the queue.
Stevens提出的解决方案只是增加backlog。这样做的问题在于它假定如果应用程序希望调整backlog,不仅要考虑如何处理新建立的传入连接,还有考虑诸如往返时间等流量特性。Linux中的实现有效地分离了这两个问题:应用程序只负责调整backlog,使其可以足够快地接收(accept)连接,避免填满accept队列; 系统管理员则可以根据流量特性调整/proc/sys/net/ipv4/tcp_max_syn_backlog
【翻译】TCP backlog在Linux中的工作原理的更多相关文章
- Linux中Postfix邮件原理介绍(一)
邮件相关协议 SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议, 工作在TCP的25端口.它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式 ...
- php在web服务器中的工作原理
1.web工作原理 我是学习PHP网站建设的,那么网站在客户端和服务端的运行是网站运行的根本所在,那个这个运行过程是怎样的呢?我们一探就将! Web:终端 服务器web:我们把提供(响应)服务的计算机 ...
- Spring MVC中DispatcherServlet工作原理探究
转:http://blog.csdn.net/zhouyuqwert/article/details/6853730 下面类图将主要的类及方法抽离出来,以便查看方便,根据类的结构来说明整个请求是如何工 ...
- Java 8 中 ConcurrentHashMap工作原理的要点分析
简介: 本文主要介绍Java8中的并发容器ConcurrentHashMap的工作原理,和其它文章不同的是,本文重点分析了对不同线程的各类并发操作如get,put,remove之间是如何同步的,以及这 ...
- Java8 中 ConcurrentHashMap工作原理的要点分析
简介: 本文主要介绍Java8中的并发容器ConcurrentHashMap的工作原理,和其它文章不同的是,本文重点分析了不同线程的各类并发操作如get,put,remove之间是如何同步的,以及这些 ...
- linux中内存使用原理
首先介绍一下linux中内存是如何使用的. 当有应用需要读写磁盘数据时,由系统把相关数据从磁盘读取到内存,如果物理内存不够,则把内存中的部分数据导入到磁盘,从而把磁盘的部分空间当作虚拟内存 来使用,也 ...
- HADOOP1.X中HDFS工作原理
转载自:http://www.daniubiji.cn/archives/596 HDFS(Hadoop Distributed File System )Hadoop分布式文件系统.是根据googl ...
- Hadoop中HDFS工作原理
转自:http://blog.csdn.net/sdlyjzh/article/details/28876385 Hadoop其实并不是一个产品,而是一些独立模块的组合.主要有分布式文件系统HDFS和 ...
- js中Ajax工作原理(转)
在写这篇文章之前,曾经写过一篇关于AJAX技术的随笔,不过涉及到的方面很窄,对AJAX技术的背景.原理.优缺点等各个方面都很少涉及null.这次写这篇文章的背景是因为公司需要对内部程序员做一个培训.项 ...
随机推荐
- 使用Navicat Premium对sqlserver 2008进行表、字段及用户权限的精细化管理
在一些特殊的业务场景,我们需要对数据库进行精细化的管理,比如只能授权给某用户某个表的操作权限,最小权限法则可以保障数据库最大的安全.利用navicat这个轻量化的工具可以很轻松的解决这个问题 1.通过 ...
- PYTHON-面向对象-练习-王者荣耀 对砍游戏
# 王者荣耀 对砍游戏# 两个英雄可以对砍 如果血量小于等于0 就GG# 所需的对象# 英雄对象""" 亚瑟 属性 类型 血量 名称 技能 Q 跳起来给你一刀 伤害50 ...
- javascript 判断属性是否存在
判断一个实例是否存在某个属性的方法使用 "in" var Student = { name: "Robot", height: 1.2, sex: " ...
- 深入浅出 JavaScript 关键词 -- this
深入浅出 JavaScript 关键词 -- this 要说 JavaScript 这门语言最容易让人困惑的知识点,this 关键词肯定算一个.JavaScript 语言面世多年,一直在进化完善,现在 ...
- mysql查询不区分大小写问题分析和解决
mysql查询默认是不区分大小写的 如: select * from some_table where str=‘abc'; select * from some_table where str='A ...
- 【python】中文提取,判断,分词
参考: http://www.cnblogs.com/kaituorensheng/p/3595879.html https://github.com/fxsjy/jieba 判断是否包含中文 def ...
- python接口自动化测试五:乱码、警告、错误处理
乱码: 以content字节流输出,解码成utf-8: print(r.encoding) # 查看返回的编码格式: 去掉Warning警告: import urllib3 urllib3.dis ...
- python 全栈开发,Day134(爬虫系列之第1章-requests模块)
一.爬虫系列之第1章-requests模块 爬虫简介 概述 近年来,随着网络应用的逐渐扩展和深入,如何高效的获取网上数据成为了无数公司和个人的追求,在大数据时代,谁掌握了更多的数据,谁就可以获得更高的 ...
- python 全栈开发,Day69(Django的视图层,Django的模板层)
昨日内容回顾 相关命令: 1 创建项目 django-admin startproject 项目名称 2 创建应用 python manage.py startapp app名称 3 启动项目 pyt ...
- jdk提供的数组扩容方法:System.arraycopy
package chapter7; /* * jdk提供的扩容方法 * System.arraycopy */public class TestArrayjdk { public static voi ...