原文How TCP backlog works in Linux
水平有限,难免有错,欢迎指出!
以下为翻译:


当应用程序通过系统调用listen将一个套接字(socket)置为LISTEN状态时,需要为该套接字指定一个backlog参数,该参数通常被描述为用来限制进来的连接队列长度(queue of incoming connections)。

由于TCP协议的三次握手机制,一个进来的套接字连接在进入ESTABLISHED状态并且可以被accept调用返回给应用程序之前,会经历中间状态SYN RECEIVED(见上图)。这意味着TCP协议栈可以有两种方案来实现backlog队列:

  1. 只使用一个队列,队列大小由系统调用listen的backlog参数指定。服务端收到一个SYN数据包后将返回一个SYN/ACK数据包,同时将该连接放入到队列中。当服务端再次收到客户端返回的ACK时,该连接状态将变为ESTABLISHED,从而有资格被交给应用程序处理。
  2. 使用两个队列,一个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

转载于:https://www.cnblogs.com/sduzh/p/6654225.html

【翻译】TCP backlog在Linux中的工作原理相关推荐

  1. linux中的execlp函数的作用,我不明白execlp()在Linux中的工作原理

    这个原型: int execlp(const char *file, const char *arg, ...); 说execlp是一个可变参数函数.它需要2个const char *.其余的参数(如 ...

  2. 中间件是什么?在.NET Core中的工作原理又是怎样的呢?

    本文出自<从零开始学ASP.NET CORE MVC> 推荐文章:ASP.NET Core appsettings.json文件 ASP.NET Core 中的中间件(Middleware ...

  3. Linux 文件系统的工作原理深度透析

    磁盘为系统提供了最基本的持久化存储. 文件系统则在磁盘的基础上,提供了一个用来管理文件的树状结构. 那么,磁盘和文件系统是怎么工作的呢?又有哪些指标可以衡量它们的性能呢? 索引节点和目录项 文件系统, ...

  4. 详解JSP 中Spring工作原理及其作用

    详解JSP 中Spring工作原理及其作用 1.springmvc请所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责负责对请求进行真正的处理工作. 2.Dispat ...

  5. 电源设计中电容的工作原理及选用

    电源往往是我们在 电路设计过程中最容易忽略的环节.作为一款优秀的设计,电源设计应当是很重要的,它很大程度影响了整个系统的性能和成本. 电源设计中的 电容使用,往往又是电源设计中最容易被忽略的地方. 一 ...

  6. 零线与地线既然都是接地的,它们的区别在哪里,零线在变压器中的工作原理是怎样的?

    零线与地线既然都是接地的,它们的区别在哪里,零线在变压器中的工作原理是怎样的? 2012-4-16 09:54 提问者: qq1034665088 |  浏览次数:574次 我来帮他解答     20 ...

  7. OCR算法:车牌识别在停车系统中的工作原理

    车牌识别在停车场系统中的工作原理 1.车辆查看:可采用埋地线圈查看.红外查看.雷达查看技术.道闸视频查看等多种办法感知车辆的通过,并触发图像搜集抓拍. 2.图像搜集:通过高清摄像抓拍主机对通行车辆进行 ...

  8. linux命令tcp和tt,Linux中tcpdump命令起什么作用呢?

    摘要: 下文讲述Linux中tcpdump的功能说明,如下所示: tcpdump是一款sniffer工具 tcpdump是linux下的抓包工具嗅探器. tcpdump命令功能: 用于输出所有经过网络 ...

  9. linux 在命令行中复制的快捷键_在 Linux 中加速工作的键盘快捷键 | Linux 中国

    学习键盘快捷键将使生产率提高 3.3%-- S Sathyanarayanan 操作鼠标.键盘和菜单会占用我们很多时间,这些可以使用键盘快捷键来节省时间.这不仅节省时间,还可以使用户更高效. 你是否意 ...

最新文章

  1. 【Netty】option(ChannelOption.SO_KEEPALIVE, true) socket参数详解:KeepAlive
  2. unix网络编程 ubuntu下搭建环境编译源码
  3. linux目录统计编程,linux系统编程----统计一个目录下的普通文件个数
  4. Oracle性能调整的误区
  5. java maven 操作 收集的一些命令
  6. idea android 模块,IntelliJ IDEA 12 - 新的Android应用程序模块向导失败,“无法找到模块的资源目录”...
  7. 本地更新github项目_GitHub开源项目2019-03-29更新精选
  8. 在Linux中GNU的名词解释,GNU在GNU / Linux操作系统中的主要贡献是什么...
  9. 字节跳动冬令营报名启动,邀你一起备战ICPC世界总决赛!
  10. Android 6 ti dsp,TI DSP TMS320C66x学习笔记之内联指令(c6x.h中文注释)(六)
  11. C#中WinForm窗体事件的执行次序
  12. ansys_apdl使用教程
  13. 大容量U盘计算机会不识别吗,电脑无法识别U盘?学会这5步操作,不求人自己也能解决...
  14. Android黑白照片上色APP,黑白图片上色工具
  15. 利用Mono-cecil实现.NET程序的重新签名,重新链接相关库的引用
  16. Android管理cookie,Android中的cookie管理简介
  17. Windows10超级好用的虚拟机
  18. 洛谷P6685 可持久化动态仙人掌的直径问题
  19. 系统从win7更新到win10没有声音(扬声器一直显示未插入)
  20. java访问excel表格_Java读取excel表格(示例代码)

热门文章

  1. Android中ExpandableListView的使用
  2. jQuery 操作DOM总结,DOM Core操作,HTML-DOM操作和CSS-DOM操作
  3. struts2+Hibernate实现用户登陆功能
  4. scrollview 实现滑动到底部再滑动加载数据的功能
  5. Mac下使用OpenCV
  6. 被墙怎么搭建安卓开发环境
  7. golang多语言支持
  8. C#之windows桌面软件第十三课:C#中常用的类有哪些?构造函数怎么用?
  9. STM32固件库文件树及构成详解
  10. spring22:Aspectj实现环绕通知@Around