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

1,061 阅读3分钟

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

1 半连接和全连接队列概念


在 TCP 三次握手的过程中,Linux 内核会维护两个队列,分别是:

  • 半连接队列 (syns queue)
  • 全连接队列 (accept queue)

正常的 TCP 三次握手过程:

image.png

  1. Client 端向 Server 端发送 SYN 发起握手,Client 端进入 SYN_SENT 状态
  2. Server 端收到 Client 端的 SYN 请求后,Server 端进入 SYN_RECV 状态,此时内核会将连接存储到半连接队列(SYN Queue),并向 Client 端回复 SYN+ACK
  3. Client 端收到 Server 端的 SYN+ACK 后,Client 端回复 ACK 并进入 ESTABLISHED 状态
  4. Server 端收到 Client 端的 ACK 后,内核将连接从半连接队列(SYN Queue)中取出,添加到全连接队列(Accept Queue),Server 端进入 ESTABLISHED 状态
  5. Server 端应用进程调用 accept 函数时,将连接从全连接队列(Accept Queue)中取出

半连接队列和全连接队列都有长度大小限制,超过限制时内核会将连接 Drop 丢弃或者返回 RST 包。

2 全连接队列大小


2.1 查看半连接队列长度

通过 ss 命令可以查看到当前 TCP 全连接队列的信息

# -n 不解析服务名称 
# -t 只显示 tcp sockets 
# -l 显示正在监听(LISTEN)的 sockets 
 
$ ss -lnt 
State      Recv-Q Send-Q    Local Address:Port         Peer Address:Port 
LISTEN     0      128       [::]:2380                  [::]:* 
LISTEN     0      128       [::]:80                    [::]:* 
LISTEN     0      128       [::]:8080                  [::]:* 
LISTEN     0      128       [::]:8090                  [::]:* 
 
$ ss -nt 
State      Recv-Q Send-Q    Local Address:Port         Peer Address:Port 
ESTAB      0      0         [::ffff:33.9.95.134]:80                   [::ffff:33.51.103.59]:47452 
ESTAB      0      536       [::ffff:33.9.95.134]:80                  [::ffff:33.43.108.144]:37656 
ESTAB      0      0         [::ffff:33.9.95.134]:80                   [::ffff:33.51.103.59]:38130 
ESTAB      0      536       [::ffff:33.9.95.134]:80                   [::ffff:33.51.103.59]:38280 

对于 LISTEN 状态的 socket

  • Recv-Q:当前全连接队列的大小,已完成三次握手等待应用程序 accept() 的 TCP 连接
  • Send-Q:全连接队列的最大长度,即全连接队列的大小

对于非 LISTEN 状态的 socket

  • Recv-Q:已收到但未被应用程序读取的字节数
  • Send-Q:已发送但未收到确认的字节数

2.2 设置全连接队列大小

全连接队列的大小取决于min(backlog, somaxconn)

  • backlog是在socket创建的时候传入的, 是 TCP 协议中 listen 函数的参数之一,即 int listen(int sockfd, int backlog) 函数中的 backlog 大小
  • somaxconn是一个os级别的系统参数, 由 /proc/sys/net/core/somaxconn 指定

3 半连接队列大小


3.1 查看半连接队列长度

查看当前 tcp 半连接队列长度没有命令,可以抓住 TCP 半连接的特点,服务端处于 SYN_RECV 状态的 TCP 连接,就是 TCP 半连接队列。

 # 查看当前 tcp 半连接队列长度
    > netstat -napt | grep SYN_RECV | wc -l
    256  # 表示当前 处于半连接状态的tcp 连接有256个

3.1 设置半连接队列大小

而半连接队列的大小取决于max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog),但是实际半连接队列的最大长度不一定由 tcp_max_syn_backlog 值决定的。

全连接队列的最大值是 sk_max_ack_backlog 变量,sk_max_ack_backlog 实际上是在 listen() 源码里指定的,也就是 min(somaxconn, backlog); 半连接队列的最大值是 max_qlen_log

  • 当 max_syn_backlog > min(somaxconn, backlog) 时, 半连接队列最大值 max_qlen_log = min(somaxconn, backlog) * 2
  • 当 max_syn_backlog < min(somaxconn, backlog) 时, 半连接队列最大值 max_qlen_log = max_syn_backlog * 2

注意max_qlen_log 是理论半连接队列最大值,并不一定代表服务端处于 SYN_REVC 状态的最大个数。

4 队列溢出

4.1 查看队列溢出

通过netstat -s命令可以查看 TCP 半连接队列、全连接队列的溢出情况

$ netstat -s | egrep -i  "listen|LISTEN"
    189088 times the listen queue of a socket overflowed 
    30140232 SYNs to LISTEN sockets dropped 

overflowed表示全连接队列溢出的次数,sockets dropped表示半连接队列溢出的次数

4.2 全连接队列溢出

全连接队列满DROP请求是默认行为,可以通过设置 使 Server 端在全连接队列满时,向 Client 端发送 RST 报文。参数 tcpabort_on_overflow有两种可选值:

  • 0:如果全连接队列满了,Server 端 DROP Client 端回复的 ACK
  • 1:如果全连接队列满了,Server 端向 Client 端发送 RST 报文,终止 TCP socket 链接

全连接队列溢出时,客户端连接不上服务端:

  1. tcp_abort_on_overflow = 1,那么在客户端异常中会看到很多connection reset by peer的错误,可以将溢出尽快通知客户端。

  2. tcp_abort_on_overflow = 0,服务器会丢掉了 ACK,而处于ESTABLISHED状态的客户端会重发请求,如果服务器上只是短暂繁忙造成 accept 队列满,当有空位时,服务器端收到重复请求后可以成功建立连接。

通常情况下,应当把tcpabort_on_overflow设置为 0,因为这样更有利于应对突发流量,高连接建立的成功率。所以,tcpabort_on_overflow设为 0 可以提高连接建立的成功率。而设置为 1 可以尽快通知客户端。

其他注意

  • 能够进入全连接队列的 Socket最大数量 = 配置的全连接队列最大长度 + 1
  • 服务端常用servlet容器,容器的连接队列大小,在tomcat中backlog叫做acceptCount,在jetty里面则是acceptQueueSize

4.3 半连接队列溢出

  • 如果没有开启 tcp_syncookies,并且半连接队列满了,则会丢弃;
  • 如果没有开启tcp_syncookies,并且 max_syn_backlog - 当前半连接队列长度 < (max_syn_backlog >> 2),则会丢弃。
  • 若全连接队列满了,且没有重传 SYN+ACK 包的连接请求多于 1 个,则会丢弃;

4.4 防御 SYN 攻击

方式一:增大半连接队列: 要想增大半连接队列,我们得知不能只单纯增大 tcp_max_syn_backlog 的值,还需一同增大 somaxconn 和 backlog,也就是增大全连接队列。

方式二:开启 tcp_syncookies 功能: 开启 syncookies 功能就可以在不使用 SYN 半连接队列的情况下成功建立连接。 syncookies 参数主要有以下三个值:

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

方式三:减少 SYN+ACK 重传次数: 服务端受到 SYN 攻击时,会有大量处于 SYN_REVC 状态的 TCP 连接,处于这个状态的 TCP 会重传 SYN+ACK 。减少 SYN+ACK 的重传次数,可以加快处于 SYN_REVC 状态的 TCP 连接断开。