错误信息:
Caused by: java.net.SocketException: Connection reset by peer: socket write error
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:111)
at java.net.SocketOutputStream.write(SocketOutputStream.java:155)
at org.apache.coyote.http11.InternalOutputBuffer.realWriteBytes(InternalOutputBuffer.java:216)
at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:487)
at org.apache.coyote.http11.InternalOutputBuffer.flush(InternalOutputBuffer.java:120)
at org.apache.coyote.http11.AbstractHttp11Processor.action(AbstractHttp11Processor.java:828)
at org.apache.coyote.Response.action(Response.java:173)
at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:366)
... 90 more
错误原因:
从日志中可以看到是Socket套接字在write 数据时抛出了该错误。导致“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连接成功)状态,完成三次握手。
-
服务器返回了“RST”时,如果此时客户端正在从Socket套接字的输出流中读数据则会提示Connection reset”;
-
服务器返回了“RST”时,如果此时客户端正在往Socket套接字的输入流中写数据则会提示“Connection reset by peer”。
Socket默认连接60秒,60秒之内没有进行心跳交互,即读写数据,就会自动关闭连接。如果一端的Socket被关闭(或主动关闭,或因为异常退出而 引起的关闭),另一端仍发送数据,发送的第一个数据包引发该异常(Connect reset by peer)。如果一端退出,但退出时并未关闭该连接,另一端如果在从连接中读数据则抛出该异常(Connection reset)。
简单的说就是在连接断开后的读和写操作引起的。
原因1
使用 Java NIO 建立 Socket 服务端,当客户端意外关闭的情况,不是发送指定指令通知服务器退出,就会产生此错误。
原因2
客户端再发起请求后没有等服务器端相应完,点击了stop按钮,导致服务器端接收到取消请求(频繁的刷新就会产生此问题)。通常情况下是不会有这么无聊的用户,出现这种情况可能是由于用户提交了请求,服务器端相应缓慢,比如业务逻辑有问题等原因,导致页面过了很久也没有刷新出来,用户就有可能取消或重新发起请求。 这种错误是合理范围内的,无法避免的,不必关心它。
原因3
Tomcat服务器在接受用户请求的时候,有其自身的处理能力,线程、服务器等各个资源限制,超出Tomcat承载范围的请求,就会被tomcat停掉,也可能产生该错误。服务器的并发连接数超过了其承载量,服务器会将其中一些连接关闭
原因4
Linux的线程机制会产生JVM出错的问题,特别是在连接高峰期间经常出现这样的问题,tomcat在linux下也出现类似情况。
原因5
如果网络连接通过防火墙,而防火墙一般都会有超时的机制,在网络连接长时间不传输数据时,会关闭这个TCP的会话,关闭后在读写,就会导致异常。 如果关闭防火墙,解决了问题,需要重新配置防火墙,或者自己编写程序实现TCP的长连接。实现TCP的长连接,需要自己定义心跳协议,每隔一段时间,发送一次心跳协议,双方维持连接。
解决办法:
1.出错了重试
这种方案可以简单防止“Connection reset”错误,然后如果服务不是“幂等”的则不能使用该方法
2.客户端和服务器统一使用TCP长连接
客户端和服务器统一使用TCP长连接:客户端使用TCP长连接很容易配置(直接设置HttpClient就好),而服务器配置长连接就比较麻烦了,就拿tomcat来说,需要设置tomcat的maxKeepAliveRequests、connectionTimeout等参数。另外如果使用了nginx进行反向代理或负载均衡,此时也需要配置nginx以支持长连接(nginx默认是对客户端使用长连接,对服务器使用短连接)。
使用长连接可以避免每次建立TCP连接的三次握手而节约一定的时间,但是我这边由于是内网,客户端和服务器的3次握手很快,大约只需1ms。ping一下大约0.93ms(一次往返);三次握手也是一次往返(第三次握手不用返回)。根据80/20原理,1ms可以忽略不计;又考虑到长连接的扩展性不如短连接好、修改nginx和tomcat的配置代价很大(所有后台服务都需要修改);所以这里并没有使用长连接。
3.客户端和服务器统一使用TCP短连接
客户端和服务器统一使用TCP短连接:我这边正是这么干的,而使用短连接既不用改nginx配置,也不用改tomcat配置,只需在使用HttpClient时使用http1.0协议并增加http请求的header信息(Connection: Close)
httpGet.setProtocolVersion(HttpVersion.HTTP_1_0);
httpGet.addHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_CLOSE);
文章参考:
https://my.oschina.net/xionghui/blog/508758
https://blog.csdn.net/testcs_dn/article/details/78707219