Connection reset 原因 接收到TCP-RST包
导致“Connection reset”的其中一个原因是服务器端因为某种原因关闭了Connection,而客户端依然在读写数据,此时服务器会返回复位标志“RST”,然后此时客户端就会提示“java.net.SocketException: Connection reset”。
什么是复位标记RST:
TCP建立连接时需要三次握手,在释放连接需要四次挥手;
例如三次握手的过程如下:
第一次握手:客户端发送SYN包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;
第二次握手:服务器收到SYN包,并会确认客户的SYN(ack=j+1),
同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),
此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
可以看到握手时会在客户端和服务器之间传递一些TCP头信息,比如ACK标志、SYN标志以及挥手时的FIN标志等。
除了以上这些常见的标志头信息,还有另外一些标志头信息,比如推标志PSH、复位标志RST等。其中复位标志RST的作用就是“复位相应的TCP连接”。
TCP连接和释放时还有许多细节,比如半连接状态、半关闭状态等。详情请参考这方面的巨著《TCP/IP详解》和《UNIX网络编程》。
前面说到出现“Connection reset”的原因是服务器关闭了Connection[调用了Socket.close()方法]。
大家可能有疑问了:服务器关闭了Connection为什么会返回“RST”而不是返回“FIN”标志。原因在于Socket.close()方法的语义和TCP的“FIN”标志语义不一样:
发送TCP的“FIN”标志表示我不再发送数据了,
而Socket.close()表示我不在发送也不接受数据了。
问题就出在“我不接受数据” 上,如果此时客户端还往服务器发送数据,服务器内核接收到数据,但是发现此时Socket已经close了,则会返回“RST”标志给客户端。当然,此时客户端就会提示:“Connection reset”。
详细说明可以参考oracle的有关文档:http://docs.oracle.com/javase/1.5.0/docs/guide/net/articles/connection_release.html。
Connection reset
导致“Connection reset”的其中一个原因是服务器端因为某种原因关闭了Connection,而客户端依然在读写数据,此时服务器会返回复位标志“RST”,然后此时客户端就会提示“java.net.SocketException: Connection reset”。
另一个可能导致的“Connection reset”的原因是服务器设置了Socket.setLinger (true, 0)。但我检查过线上的tomcat配置,是没有使用该设置的,而且线上的服务器都使用了nginx进行反向代理,所以并不是该原因导致的。关于该原因上面的oracle文档也谈到了并给出了解释。
Connection reset by peer
此外啰嗦一下,另外还有一种比较常见的错误“Connection reset by peer”,该错误和“Connection reset”是有区别的:
服务器返回了“RST”时,如果此时客户端正在从Socket套接字的输出流中读数据则会提示Connection reset”;
服务器返回了“RST”时,如果此时客户端正在往Socket套接字的输入流中写数据则会提示“Connection reset by peer”。
解决Connection reset几种方案
前面谈到了导致“Connection reset”的原因,而具体的解决方案有如下几种:
1. 出错了重试;
2. 客户端和服务器统一使用TCP长连接;
3. 客户端和服务器统一使用TCP短连接。
首先是出错了重试:这种方案可以简单防止“Connection reset”错误,然后如果服务不是“幂等”的则不能使用该方法;比如提交订单操作就不是幂等的,如果使用重试则可能造成重复提单。
然后是客户端和服务器统一使用TCP长连接:客户端使用TCP长连接很容易配置(直接设置HttpClient就好),而服务器配置长连接就比较麻烦了,就拿tomcat来说,需要设置tomcat的maxKeepAliveRequests、connectionTimeout等参数。另外如果使用了nginx进行反向代理或负载均衡,此时也需要配置nginx以支持长连接(nginx默认是对客户端使用长连接,对服务器使用短连接)。
使用长连接可以避免每次建立TCP连接的三次握手而节约一定的时间,但是我这边由于是内网,客户端和服务器的3次握手很快,大约只需1ms。ping一下大约0.93ms(一次往返);三次握手也是一次往返(第三次握手不用返回)。根据80/20原理,1ms可以忽略不计;又考虑到长连接的扩展性不如短连接好、修改nginx和tomcat的配置代价很大(所有后台服务都需要修改);所以这里并没有使用长连接。
正常情况为tcp四层握手关闭连接,而rst基本都是异常情况,使用 ping 可以看到丢包情况
RST复位标志发送原因几种可能整理如下:
-
被GFW拦截
防火长城(英文名称Great Firewall of China,简写为Great Firewall,缩写GFW),也称中国防火墙或中国国家防火墙,指中华人民共和国政府在其管辖因特网内部建立的多套网络审查系统的总称,包括相关行政审查系统。
-
对方端口未打开,发生在连接建立
-
全连接队列已满,发生在连接建立
当全连接队列已满就会根据tcp_abort_on_overflow策略进行处理。
Linux 可通过 /proc/sys/net/ipv4/tcp_abort_on_overflow 进行配置。
当tcp_abort_on_overflow=0,服务accept 队列满了,客户端发来ack,服务端直接丢弃该ACK,此时服务端处于【syn_rcvd】的状态,客户端处于【established】的状态。在该状态下会有一个定时器重传服务端 SYN/ACK 给客户端(不超过 /proc/sys/net/ipv4/tcp_synack_retries 指定的次数,Linux下默认5)。超过后,服务器不在重传,后续也不会有任何动作。如果此时客户端发送数据过来,服务端会返回RST。(这也就是我们的异常原因了)
当tcp_abort_on_overflow=1,服务端accept队列满了,客户端发来ack,服务端直接返回RST通知client,表示废掉这个握手过程和这个连接,client会报connection reset by peer。
-
close Socket 时recv buffer 不为空
例如,客户端发了两个请求,服务器只从buffer 读取第一个请求处理完就关闭连接,tcp层认为数据没有正确提交到应用,使用rst关闭连接。
-
移动链路
移动网络下,国内是有5分钟后就回收信令,也就是IM产品,如果心跳>5分钟后服务器再给客户端发消息,就会收到rst。也要查移动网络下IM 保持<5min 心跳。
-
负载等设备
负载设备需要维护连接转发策略,长时间无流量,连接也会被清除,而且很多都不告诉两层机器,新的包过来时才通告rst。
Apple push 服务也有这个问题,而且是不可预期的偶发性连接被rst;rst 前第一个消息write 是成功的,而第二条写才会告诉你连接被重置,
曾经被它折腾没辙,因此打开每2秒一次tcp keepalive,固定5分钟tcp连接回收,而且发现连接出错时,重发之前10s内消息。
-
SO_LINGER 应用强制使用rst 关闭
该选项会直接丢弃未发送完毕的send buffer,可能造成业务错误,慎用; 当然内网服务间http client 在收到应该时主动关闭,使用改选项,会节省资源。
好像曾经测试过haproxy 某种配置下,会使用rst关闭连接,少了网络交互而且没有TIME_WAIT 问题
-
超过超时重传次数、网络暂时不可达(同3)
-
TIME_WAIT 状态
tw_recycle = 1 时,sync timestamps 比上次小时,会被rst
-
设置 connect_timeout
应用设置了连接超时,sync 未完成时超时了,会发送rst终止连接。
-
非正常包
连接已经关闭,seq 不正确等
-
keepalive 超时
公网服务tcp keepalive 最好别打开;移动网络下会增加网络负担,切容易掉线;非移动网络核心ISP设备也不一定都支持keepalive,曾经也发现过广州那边有个核心节点就不支持。
-
数据错误,不是按照既定序列号发送数据
-
在一个已关闭的socket上接收数据
-
服务器关闭或异常终止了连接,由于网络问题,客户端没有收到服务器的关闭请求,这称为TCP半打开连接。就算重启服务器,也没有连接信息。如果客户端向提其写入数据,对方就会回应一个RST报文段。
-
TCP在建链过程中收到异常报文时,会发送RST报文:
如在Listen状态时收到的SYN报文中有ACK标记,SYN_SEND状态时收到SYN|ACK报文中ACK序号不对,etc
-
TCP在建链时,发出SYN报文之后,60秒之内都没有收到对端的相应报文,会发送RST报文。
-
TCP报文在连续重传阈值次数之后,都没有收到对端相应报文时,会发送RST报文。(同3、8)
-
KeepAlive报文在连续发送5次之后,都没有收到对端的KeeAliveReply报文时,会发送RST报文。
-
当socket设置了Linger选项,并Linger时间为0时,当调用close来关闭socket时,也会向对端发送RST报文。(同7)