miller
发布于

connection reset by peer,TCP三次握手后服务端发送RST

问题描述

这两天用Go做一个比较简单的task:后端有HTTPServer和TCPServer。客户端通过http接入到HTTPServer,HTTPServer通过RPC将请求发送到TCPServer,所有的业务逻辑都由TCPServer处理。
压测:自己的mac电脑(CPU:Intel i7, 4核,2.7GHz。内存:16G),硬件够用。客户端用Go编写,1个goruntine启动一个HTTPClient往HTTPServer发送http请求。每个HTTPClient限定为一个HTTP长链接。
问题:压到400个HTTPClient,出现一些错误提示“read: connection reset by peer”。

问题定位以及原因

“connection reset by peer”的含义是往对端写数据的时候,对端提示已经关闭了连接。一般往一个已经被关闭的socket写会提示这个错误。但是通过log分析,服务端没有应用层面的close,客户端也没有应用层面的write。抓包发现客户端建立TCP完成3次握手后,服务端立刻就回了RST。如下图:

这个抓包很好的反应了压测中的现象:错误提示connection reset by peer,但是应用层并没有任何的读写,TCP三次握手后服务端直接通过RST关闭了连接。RST的情况见的多,这种情况着实没有遇到过。最后N次baidu google,终于找到答案。

TCP三次握手后服务端直接RST的真相

内核中处理TCP连接时维护着两个队列:SYN队列和ACCEPT队列,在建立连接过程中,服务端内核的处理过程如下:
(1)客户端使用connect调用向服务端发起TCP连接,服务端内核将此连接信息放入SYN队列,返回SYN-ACK
(2)服务端内核收到客户端的ACK后,将此连接从SYN队列中取出,放入ACCEPT队列
(3)服务端应用层调用accept函数将连接从ACCEPT队列中取出
上述抓包说明,3次握手已经完成。但是应用层accept并没有返回,说明问题出在ACCEPT队列中。那么什么情况下,内核TCP协议栈会在三次握手完成后发RST呢?原因就是ACCEPT队列满了,上述(2)中,服务端内核收到客户端的ACK后将连接放入ACCEPT队列失败,就有可能回RST拒绝连接。
进一步来看Linux协议栈的一些逻辑:SYN队列和ACCEPT队列的长度是有限制的,SYN队列长度由内核参数tcp_max_syn_backlog决定,ACCEPT队列长度可以在调用listen(backlog)通过backlog,但总最大值受到内核参数somaxconn(/proc/sys/net/core/somaxconn)限制。若SYN队列满了,新的SYN包会被直接丢弃。若ACCEPT队列满了,建立成功的连接不会从SYN队列中移除,同时也不会拒绝新的连接,这会加剧SYN队列的增长,最终会导致SYN队列的溢出。当ACCEPT队列溢出之后,只要打开tcp_abort_on_overflow内核参数(默认为0,关闭),建立连接后直接回RST,拒绝连接(可以通过/proc/net/netstat中ListenOverflows和ListenDrops查看拒绝的数目)。

客户端 连接放缓后,错误消失

浏览 (421)
点赞
收藏
评论