当前位置:   article > 正文

TCP 的半连接队列和全连接队列_tcp半连接队列

tcp半连接队列

TCP 的半连接队列和全连接队列

什么是半连接队列?什么是全连接队列?

半连接队列是指:用于存储处于 SYN_RECV 状态的连接的队列

当内核收到一个 SYN 报文,就将该连接放到半连接队列,并发送 ACK 给对方

全连接队列是指:用于存储已经建立好 TCP 连接,但还未被应用调用 accept 取走的连接的队列

当内核收到 SYN 报文对应的 ACK 后,就将该连接从半连接队列中取出,并放到全连接队列,等待应用程序调用 accept 取走

在这里插入图片描述

半连接队列与全连接队列的本质

虽然都有「队列」二字,但二者 实际上都不是队列

  • 当一个连接请求到来,内核会将连接元数据存到 半连接队列,发送 ack
  • 收到某个连接的 ack 的 ack 后,从半连接队列中取出该连接,发送 ack,并放到 全连接队列
  • 服务进程调用 accept,取出一个建立好的 TCP 连接

“收到某个连接的 ack 的 ack 后,从半连接队列中取出该连接”的「取出」过程是一个 随机 的过程,因为这个 ack 的顺序并不固定

如果半连接队列设计成线性结构,那么取出对应连接就需要 O(n) 的时间

因此,半连接队列实际上是一个哈希表,取出对应连接的期望时间是 O(1)

而全连接队列实际上是一个链表,当然,也可以理解成队列(毕竟队列也有基于链表实现的)

在 Linux 上查看「全连接队列」的大小与容量

可以使用 ss -lnt 查看某个全连接队列的大小:

root@SkyLee:~# ss -lnt
State   Recv-Q   Send-Q     Local Address:Port      Peer Address:Port
LISTEN  0        4096                   *:1145                   *:*
  • 1
  • 2
  • 3
  • Recv-Q:当前监听的 socket,全连接队列中,没有被取走的 socket 数量(理解成 size)
  • Send-Q:当前监听的 socket,全连接队列 size 的最大值(理解成 capacity)

「全连接队列」溢出,会发生什么?

当全连接队列满了以后,如果还有新的连接请求到来,内核会 默认直接丢弃 该连接,这个行为可以通过修改 /proc/sys/net/ipv4/tcp_abort_on_overflow 的值来改变:

root@SkyLee:~# cat /proc/sys/net/ipv4/tcp_abort_on_overflow
0
  • 1
  • 2
  • 0:直接丢弃
  • 1:发送 RST 报文给对方

为啥默认值为 0?直接丢弃,不会占用客户端的资源吗?

设置为 0,有利于提高请求的成功率

进入全连接队列,意味着客户端已经进入 ESTABLISHED 状态

当用户给服务器发请求时,只要服务器不 Response,客户端就会不断重试发送第三次握手的 ACK,只要在重试次数到达上限前,服务器调用了 accept 接收了该连接,就可以处理用户的请求了

因此,除非认为服务器需要很长时间才来得及 accept(超过了重传的上限时间),不要将 tcp_abort_on_overflow 修改成 1

「全连接队列」的容量如何修改?

经过上面的分析,可以发现,如果全连接队列的 capacity:

  • 过小,可能造成 QPS 上不去
  • 过大,可能造成连接数过多,服务器承受不了

有些时候,在生产环境发现 QPS 上不去,硬件又没吃满,排查不出问题,就可以考虑是不是全连接队列的 capacity 太小了

那么,这个容量应该怎么修改呢?

两个配置:

  • listen 中的 backlog
  • /proc/sys/net/core/somaxconn

capacity 的值为二者的 最小值

在 Linux 上查看半连接队列的大小和容量

可以使用 netstat -natp | grep SYN_RECV | grep 1145 | wc -l 来查看 1145 端口上的,处于 SYN_RECV 状态的连接数量,间接的查看半连接队列的大小

netstat -natp | grep SYN_RECV | grep 1145 | wc -l
0
  • 1
  • 2

目前没有一个命令可以直接查看半连接队列的容量,但是如果想查看的话:

  • 可以使用工具(如 hping3)对想查看的 server socket 发起 SYN 攻击,把半连接队列打满
  • 再使用上面的命令查看,就可以间接的查看半连接队列的 capacity

「半连接队列」溢出,会发生什么?

当半连接队列满了以后,如果还有新的连接请求到来,并且 没有启用 syn_cookies,那么,新的连接请求 将会被抛弃

但是,可以通过启用 syn_cookies,来避免抛弃新的连接请求

在这里插入图片描述

图片来自小林 coding

syncookies 参数主要有以下三个值:

  • 0,表示关闭该功能;
  • 1,表示仅当半连接队列放不下时,再启用它;
  • 2,表示无条件开启功能

应对 SYN 攻击,可以将 syn_cookies 设置为 1

root@SkyLee:~# echo 1 > /proc/sys/net/ipv4/tcp_syncookies
  • 1

「半连接队列」的容量如何确定?

首先,是网上经常谈到的 /proc/sys/net/ipv4/tcp_max_syn_backlog 参数

root@SkyLee:~# cat /proc/sys/net/ipv4/tcp_max_syn_backlog
1024
  • 1
  • 2

但是,半连接队列的容量在不同的 Linux 版本上,有所不同

下面以内核版本为 2.6.32 的 Linux 为例,说说如何确定半连接队列的容量

第一个因素就是上面提到的 tcp_max_syn_backlog

第二个因素是 全连接队列的容量

是的,全连接队列的容量也会影响半连接队列,具体算法如下:

  • 如果 tcp_max_syn_backlog > min(somaxconn, listen_backlog),即全连接队列的大小,那么 max_qlen_log = min(somaxconn, listen_backlog) * 2
  • 否则,max_qlen_log = tcp_max_syn_backlog * 2

简单来说,max_qlen_log 的值为 min(全连接队列容量,tcp_max_syn_backlog) * 2

max_qlen_log 的值就是 理论 半连接队列的容量

但实际上,如果 size > tcp_max_syn_backlog - (tcp_max_syn_backlog >> 2),连接也会被抛弃

因此,2.6.32 版本的 Linux 实际的 capacity 的值的计算公式 为:

min(tcp_max_syn_backlog - (tcp_max_syn_backlog >> 2), min(tcp_max_syn_backlog, min(somaxconn, listen_backlog)) * 2)
  • 1

在 2.2 以前的 Linux 中,理论 半连接队列的大小为 listen 中的 backlog
在 5.0 版本的 Linux,理论 半连接队列的大小就是全连接队列的大小

总结一波:

  • 不同版本的 Linux,半连接队列的 capacity 的计算方式不同
  • 但半连接队列的 capacity 还是要受到全连接队列大小的影响
  • 为了防止 SYN 攻击,可以启用 syn_cookies
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/513677
推荐阅读
相关标签
  

闽ICP备14008679号