基本概念

当服务端调用 listen 函数时,TCP 的状态被从 CLOSE 状态变为 LISTEN,于此同时内核创建了两个队列:

  • 半连接队列(Incomplete connection queue),又称 SYN 队列
  • 全连接队列(Completed connection queue),又称 Accept 队列

image.png

半连接队列(SYN Queue)

  1. 当客户端发起 SYN 到服务端,服务端收到以后会回 ACK 和自己的 SYN。这时服务端这边的 TCP 从 listen 状态变为 SYN_RCVD (SYN Received),此时会将这个连接信息放入「半连接队列」,半连接队列也被称为 SYN Queue,存储的是 “inbound SYN packets”。

image.png

  1. 服务端回复 SYN+ACK 包以后等待客户端回复 ACK,同时开启一个定时器,如果超时还未收到 ACK 会进行 SYN+ACK 的重传,重传的次数由 tcp_synack_retries 值确定。在 CentOS 上这个值等于 5。
  2. 一旦收到客户端的 ACK,服务端就开始尝试把它加入另外一个全连接队列(Accept Queue)。

半连接队列的大小的计算

下面给了几个 somaxconn、max_syn_backlog、backlog 三者之间不同组合的最终半连接队列大小值。

image.png

模拟半连接队列占满

image.png

全连接队列(Accept Queue)

listen 函数的第二个参数 backlog 用来设置全连接队列大小,但不一定就会选用这一个 backlog 值,还受限于 somaxconn.

如果全连接队列满,内核会舍弃掉 client 发过来的 ack(应用层会认为此时连接还未完全建立)

我们来模拟一下全连接队列满的情况。因为只有 accept 才会移除全连接的队列,所以如果我们只 listen,不调用 accept,那么很快全连接就可以被占满。

image.png

  1. 客户端 A 发起 SYN 到服务端 B 的 9090 端口,开始三次握手的第一步
  2. 服务器 B 马上回复了 ACK + SYN,此时 服务器 B socket处于 SYN_RCVD 状态
  3. 客户端 A 收到服务器 B 的 ACK + SYN,发送三次握手最后一步的 ACK 给服务器 B,自己此时处于 ESTABLISHED 状态,与此同时,由于服务器 B 的全连接队列满,它会丢掉这个 ACK,连接还未建立
  4. 服务端 B 因为认为没有收到 ACK,以为是自己在 2 中的 SYN + ACK 在传输过程中丢掉了,所以开始重传,期待客户端能重新回复 ACK。
  5. 客户端 A 收到 B 的 SYN + ACK 以后,确实马上回复了 ACK

6 ~ 13. 但是这个 ACK 同样也会被服务器 B 丢弃,服务端 B 还是认为没有收到 ACK,继续重传重传的过程同样也是指数级退避的(1s、2s、4s、8s、16s),总共历时 31s 重传 5 次 SYN + ACK 以后,服务器 B 认为没有希望,一段时间后此条 tcp 连接就被系统回收了。

SYN+ACK重传的次数是由操作系统的一个文件决定的/proc/sys/net/ipv4/tcp_synack_retries,可以用 cat 查看这个文件:

  1. cat /proc/sys/net/ipv4/tcp_synack_retries
  2. 5

image.png

全连接队列的大小

全连接队列的大小是 listen 传入的 backlog 和 somaxconn 中的较小值.

ss 命令可以查看全连接队列的大小和当前等待 accept 的连接个数,执行 ss -lnt 即可,比如上面的 accept 队列满的例子中,执行 ss 命令的输出结果如下。

ss -lnt | grep :9090
State      Recv-Q Send-Q Local Address:Port               Peer Address:Port
LISTEN     51     50           *:9090                     *:*

对于 LISTEN 状态的套接字,Recv-Q 表示 accept 队列排队的连接个数,Send-Q 表示全连接队列(也就是 accept 队列)的总大小。

其它

多大的 backlog 是合适的

根据不同过的业务场景,需要做对应的调整:

  • 你如果的接口处理连接的速度要求非常高,或者在做压力测试,很有必要调高这个值
  • 如果业务接口本身性能不好,accept 取走已建连的速度较慢,那么把 backlog 调的再大也没有用,只会增加连接失败的可能性

tcp_abort_on_overflow 参数

默认情况下,全连接队列满以后,服务端会忽略客户端的 ACK,随后会重传SYN+ACK,也可以修改这种行为,这个值由/proc/sys/net/ipv4/tcp_abort_on_overflow决定。

  • tcp_abort_on_overflow 为 0 表示三次握手最后一步全连接队列满以后 server 会丢掉 client 发过来的 ACK,服务端随后会进行重传 SYN+ACK。
  • tcp_abort_on_overflow 为 1 表示全连接队列满以后服务端直接发送 RST 给客户端。