目录:
TCP建立连接是要进行三次握手,但是否完成三次握手后,服务器就处理(accept)呢?
backlog其实是一个连接队列,在Linux内核2.2之前,backlog大小包括半连接状态和全连接状态两种队列大小。
半连接状态为:服务器处于Listen状态时收到客户端SYN报文时放入半连接队列中,即SYN queue(服务器端口状态为:SYN_RCVD)。
全连接状态为:TCP的连接状态从服务器(SYN+ACK)响应客户端后,到客户端的ACK报文到达服务器之前,则一直保留在半连接状态中;当服务器接收到客户端的ACK报文后,该条目将从半连接队列搬到全连接队列尾部,即 accept queue (服务器端口状态为:ESTABLISHED)。
在Linux内核2.2之后,分离为两个backlog来分别限制半连接(SYN_RCVD状态)队列大小和全连接(ESTABLISHED状态)队列大小。
半连接队列:SYN queue 队列长度由 /proc/sys/net/ipv4/tcp_max_syn_backlog 指定,默认为2048。
全连接队列:Accept queue 队列长度由 /proc/sys/net/core/somaxconn 和使用listen函数时传入的参数,二者取最小值。默认为128。
在Linux内核2.4.25之前,是写死在代码常量 SOMAXCONN ,在Linux内核2.4.25之后,在配置文件 /proc/sys/net/core/somaxconn 中直接修改,或者在 /etc/sysctl.conf 中配置 net.core.somaxconn = 128 。
可以通过ss命令来显示
[root@localhost ~]# ss -l
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:http *:*
LISTEN 0 128 :::ssh :::*
LISTEN 0 128 *:ssh *:*
LISTEN 0 100 ::1:smtp :::*
LISTEN 0 100 127.0.0.1:smtp *:*
在LISTEN状态,其中 Send-Q 即为Accept queue的最大值,Recv-Q 则表示Accept queue中等待被服务器accept()。
队列溢出
另外,客户端connect()返回不代表TCP连接建立成功,有可能此时accept queue 已满,系统会直接丢弃后续ACK请求;客户端误以为连接已建立,开始调用等待至超时;服务器则等待ACK超时,会重传SYN+ACK 给客户端,重传次数受限 net.ipv4.tcp_synack_retries ,默认为5,表示重发5次,每次等待30~40秒,即半连接默认时间大约为180秒,该参数可以在tcp被洪水攻击是临时启用这个参数。
注:accept queue溢出,即便SYN queue没有溢出,新连接请求的SYN也可能被drop。
查看SYN queue 溢出
[root@localhost ~]# netstat -s | grep LISTEN
102324 SYNs to LISTEN sockets dropped
查看Accept queue 溢出
[root@localhost ~]# netstat -s | grep TCPBacklogDrop
TCPBacklogDrop: 2334
案例1
Nginx作为7层反向代理,客户端HTTP请求 – NGINX – 透明代理,透明代理接口存在大量慢请求;
思路
抓包,客户端同nginx通信,nginx立即返回ACK(1ms),但是3s后才返回响应数据;
Nginx同后端通信,发送SYN请求等待3s后端才响应;
结论
Backlog设置过小,导致accept queue溢出,SYN被丢弃导致3s重传;
将backlog从50增加到512,somaxconn=512;
案例2
Testserver随机生成RAR/ZIP文件,testclient访问testserver获取生成文件,所有调用采用block方式;
运行一段时间后程序永久阻塞,strace先生testclient阻塞在recvmsg;
思路
抓包观察3次握手协议,服务器返回SYN+ACK,客户端响应ACK,可服务器再次发送同样的SYN+ACK;
客户端响应的ACK丢包,而net.ipv4.tcp_synack_retries = 1;
结论
三次握手最后一步失败,server保持SYN_RECV状态等待接收ACK,client发送ACK状态变为ESTABLISHED,其认为connect()成功故接着调用recvmsg();
Syn+ack被设置为只重传一次,若这次重传仍失败,则客户端永久阻塞;
其他案例
Backlog过大,连接积压在accept queue,nginx由于连接超时而断开,PHP accept返回时连接已被客户端close,故报告PHP write Broken pipe;
Backlog过小,accept queue溢出,握手第3步的ACK被丢弃,但client认为连接成功并发送数据,造成所谓慢请求;
参考资料: