How TCP Sockets Work
TCP socket如何工作,可以参考以下几篇文章:
TCP queue
SYN Queue
服务端接收到SYN报文,将收到的请求放在SYN queue中,等发送完SYN+ACK后,等待客户端回最后一次ACK建立三次握手,成功后请求放到Accept queue中。SYN queue也称半连接队列,Accept queue叫完全连接队列。
Accpet Queue
完全三次握手的请求保存在完全连接队列,当应用程序使用**accept()**接受请求后,该请求从完全连接队列中清除并被送到应用程序中。
Queue size 大小限制
半连接队列大小max(sysctl -a|grep net.ipv4.tcp_max_syn_backlog ,64)
net.ipv4.tcp_syncookies 参数为1时,理论逻辑半连接队列无上限,不受此参数影响。
全连接队列的最大长度根本上是有listen函数定义时的size大小决定的,如listen(sfd, 1024)定义全连接队列大小限制为1024个,这个参数由应用程序backlog和系统参数net.core.somaxconn取最小值决定,即min( backlog, sysctl -a| net.core.somaxconn) ,如Nginx默认backlog为511,如果不在listen指令显式配置backlog=NUM,及时系统参数tcp_max_syn_backlog及netdev_max_backlog均调整到很大,也使用默认511作为queue size;
listen man page:
1 | The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length |
如果超出队列大小怎么办?
如果全连接队列满,会出现:进入半连接队列的SYN包将会被丢弃,进入半连接队列的ACK包将会被丢弃。以下详细讲两种现象:
-
/proc/sys/net/ipv4/tcp_abort_on_overflow 参数决定超出队列的请求如何返回,
0
表示直接丢弃(这里看资料测试过不是全部丢弃,而是随机丢弃SYN),客户端会超时重新发送SYN,直到达到客户端的连接等待超时时间,报错:Connection timeout
;1
表示丢弃后,发送RST通知客户端重置连接,客户端报错connection reset by peer
;且服务端忽略ACK报文后会根据/proc/sys/net/ipv4/tcp_synack_retries参数进行SYN+ACK报文的进行重发,重复次数由/proc/sys/net/ipv4/tcp_synack_retries决定,默认为5次,也就是(1+2+4+8+16+32=61)s,不过这时候客户端早已经超时了吧。 -
如果全连接队列满,半连接队列积攒过多,如果开启net.ipv4.tcp_syncookies=1,会系统出现如下报错,这种情况可能是全连接队列设置过小导致的:
kernel: possible SYN flooding on port 80. Sending cookies
下面引用其他博文的相关代码,如果sysctl_tcp_abort_on_overflow为0,直接丢弃什么都没做:
如果队列已满,跳到exit_overflow标签执行一些清理工作、更新/proc/net/netstat中的统计项ListenOverflows和ListenDrops,最后返回NULL。这会触发tcp_check_req函数跳到listen_overflow标签执行代码。
1 | listen_overflow: |
附录1:
附录2:一个客户正尝试连接一个已经达到其最大backlog的socket
1 | 0.000 127.0.0.1 -> 127.0.0.1 TCP 74 53302 > 9999 [SYN] Seq=0 Len=0 |
当Accept Queue满时,处在listen状态和syn_recv状态,server分别如何处理?
1 | #server端 |
结论:
- 如果Accpet Queue溢出,就算syn queue没有溢出,处在LISTEN状态,新的SYN也有可能被丢弃,也有可能被收到回复SYNACK
- 如果Accept Queue溢出,当接收SYN后并返回SYNACK,处在SYN-RECV状态再次收到ACK,ACK会直接丢弃
实验抓包图及分析:
如上图,
packet 1,client发送syn,此时server accept queue 队列满,syn 被server端丢弃
packet 2,client 1s后重传syn,重传后,server依旧丢弃
packet 3,client 1s后再次重传syn,重传后,server接收,进入SYN-RECV
packet 4,server回复syn+ack,server依旧为SYN-RECV状态
packet 5,client 收到syn+ack后,回复ACK,进入ESTAB状态 ,但是此包因为server backlog满,被丢弃
packet 6,server丢弃完client的ACK后,重传SYN+ACK
packet 7,约3s后,server继续重传syn+ack
另,测试机器backlog满导致listen drop 和 listen overflow ,见下图和下节分析
当修改sysctl -w net.ipv4.tcp_abort_on_overflow=1
时,客户端直接收到RST,不会重传
连接被丢弃的计数
如果全连接队列accept queue满了后,会出现如下计数:
- 进入半连接队列的SYN包将会被丢弃(三次握手第一个包)
- 进入半连接队列的ACK包将会被丢弃(三次握手第三个包)
- TcpExtListenOverflows 计数增加
- TcpExtListenDrops 计数增加
sk_backlog_queue
在tcp_v4_rcv()中,如果accept queue被锁住,内核会把包加到sk_backlog_queue,此时如果sk_rcv_buf不足的话会入队失败,TCPBacklogDrop counter加1
对应系统参数:
net.ipv4.tcp_wmem
net.ipv4.tcp_rmem
如果nginx出现TCPBacklogDrop计数,需要调整上述两个参数后,restart nginx.
详见下节优化部分.
Nginx 调优
- 增大backlog,
listen 80
修改为listen 80 backlog=2048 (需要和somaxcon协助修改,取最小值)
; - net.ipv4.tcp_max_syn_backlog 稍微调大,并开启net.ipv4.tcp_syncookies=1 ; (cookies开启后逻辑syn_backlog无最大值)
- 增大net.core.somaxconn 和 netdev_max_backlog,和应用程序(nginx listen backlog)配合修改,nginx默认为511;
- /proc/sys/net/ipv4/tcp_synack_retries 次数可适当减小到3次
- /proc/sys/net/ipv4/tcp_abort_on_overflow 设置为0,真出现backlog满时,不要直接发送RST重置连接,直接丢弃SYN或ACK,等待客户端重试;
so_recv_buf
相关参数优化,可解决backlogDrop问题:
1 | /proc/sys/net/ipv4/tcp_wmem的第二个值会被net.core.wmem_default覆盖,第三个值会被net.core.wmem_max覆盖,修改时请注意同步修改。 |
Nginx增加backlog后
优化so_recv_buf
相关参数优化后
net.core.somaxconn 和 listen 80 backlog 设置多少为合理
backlog 过小:
- 客户端第三次ACK被丢弃,以为ESTAB,发送数据
- connection timeout
backlog过大:
如果请求量大,导致请求积压在backlog,等处理完请求
相关案例分析
todo
感谢
本文在本人工作经验上,结合互联网大牛总结的博客,以及同事的经验,总结此文。供参考
相关文章:
https://yq.aliyun.com/articles/79972
https://mp.weixin.qq.com/s/kP6EaR5aHBa0jDpy2A8UkA
如有问题请留言!
赞赏支持一下