TCP的半连接队列&全连接队列
1 半连接和全连接队列概念
在 TCP 三次握手的过程中,Linux 内核会维护两个队列,分别是:
- 半连接队列 (syns queue)
- 全连接队列 (accept queue)
正常的 TCP 三次握手过程:
- Client 端向 Server 端发送 SYN 发起握手,Client 端进入 SYN_SENT 状态
- Server 端收到 Client 端的 SYN 请求后,Server 端进入 SYN_RECV 状态,此时内核会将连接存储到半连接队列(SYN Queue),并向 Client 端回复 SYN+ACK
- Client 端收到 Server 端的 SYN+ACK 后,Client 端回复 ACK 并进入 ESTABLISHED 状态
- Server 端收到 Client 端的 ACK 后,内核将连接从半连接队列(SYN Queue)中取出,添加到全连接队列(Accept Queue),Server 端进入 ESTABLISHED 状态
- 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 链接
全连接队列溢出时,客户端连接不上服务端:
-
tcp_abort_on_overflow = 1,那么在客户端异常中会看到很多connection reset by peer的错误,可以将溢出尽快通知客户端。
-
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 连接断开。