RST及java socket关闭后读写的各种异常

[toc]

1. RST(Reset)

TCP连接的断开有两种方式:

    1. 连接正常关闭时双方会发送FIN,经历4次挥手过程;
    1. 通过RST包异常退出,此时会丢弃缓冲区内的数据,也不会对RST响应ACK。

java中,调用Socket#close()可以关闭Socket,该方法类似Unix网络编程中的close方法,将Socket的 读写 都关闭,已经排队等待发送的数据会被尝试发送,最后(默认)发送FIN。考虑一个典型的网络事务,A向B发送数据,A发送完毕后close(),FIN发送出去;B一直read直到返回了-1,也通过close()发送FIN,4次挥手,连接关闭,一切都很和谐。

那什么时候会用RST而非FIN关闭连接呢?

  1. Socket#setSoLinger(true,0),则close时会发送RST;
  2. 如果主动关闭方缓冲区还有数据没有被应用层消费掉,close会发送RST并忽略这些数据;
  3. A向B发送数据,B已经通过close()方法关闭了Socket,虽然TCP规定半关闭状态下B仍然可以接收数据,但close动作关闭了该socket上的任何数据操作,如果此时A继续write,B将返回RST,A的该次write无法立即通知应用层(因为write仅把数据写入发送缓冲区),只会把状态保存在tcp协议栈内,下次write时才会抛出SocketException

2. 对已关闭socket读写会产生的异常

2.1 主动关闭方

close()后,无论是发送FIN/RST关闭的,之后再读写均会抛java.net.SocketException:socket is closed.

2.2 被动关闭方

被FIN关闭

  1. 写(即向”已被对方关闭的Socket”写)
    如上所说,第一次write得到RST响应但不抛异常,第二次write抛异常,ubuntu下是broken pipe (断开的管道),win7下是Software caused connection abort: socket write error
  2. 读 – 始终返回 -1

被RST关闭

读写都会抛出异常:connection reset (by peer)

重点在于:

  1. connection reset:另一端用RST主动关闭连接
  2. broken pipe / Software caused connection abort: socket write error : 对方已调用Socket#close()关闭连接,己方还在写数据

java中网络编程时很大一部分代码在做各种fail时的处理,了解各种异常发生时背后的逻辑才能正确地处理之。以上列举的只是连接关闭的异常,还有其他各种异常没有提及,以后有机会再补上。

3. 怎么避免意外的RST?

针对几种出现RST的情况:

  1. 利用应用层协议定义结构化的数据,双方对何时数据发送/接收完毕/可以安全关闭连接有明确一致的契约;
  2. close之前消费掉数据;
  3. 需要在半关闭状态下读数据时,使用shutdownOutput(),它会发送FIN但依然可以读取数据;等对方发送FIN,read()返回-1后再调用close()释放socket。

RST异常

RST包表示连接重置,用于关闭一些无用的连接,通常表示异常关闭,区别于四次挥手。

在实际开发中,我们往往会看到connection reset / connection reset by peer错误,这种情况就是RST包导致的。

端口不存在

如果像不存在的端口发出建立连接SYN请求,那么服务端发现自己并没有这个端口则会直接返回一个RST报文,用于中断连接。

主动代替FIN终止连接

一般来说,正常的连接关闭都是需要通过FIN报文实现,然而我们也可以用RST报文来代替FIN,表示直接终止连接。实际开发中,可设置SO_LINGER数值来控制,这种往往是故意的,来跳过TIMED_WAIT,提供交互效率,不闲就慎用。

客户端或服务端有一边发生了异常,该方向对端发送RST以告知关闭连接

我们上面讲的tcp队列溢出发送RST包其实也是属于这一种。这种往往是由于某些原因,一方无法再能正常处理请求连接了(比如程序崩了,队列满了),从而告知另一方关闭连接。

接收到的TCP报文不在已知的TCP连接内

比如,一方机器由于网络实在太差TCP报文失踪了,另一方关闭了该连接,然后过了许久收到了之前失踪的TCP报文,但由于对应的TCP连接已不存在,那么会直接发一个RST包以便开启新的连接。

一方长期未收到另一方的确认报文,在一定时间或重传次数后发出RST报文

这种大多也和网络环境相关了,网络环境差可能会导致更多的RST报文。

之前说过RST报文多会导致程序报错,在一个已关闭的连接上读操作会报connection reset,而在一个已关闭的连接上写操作则会报connection reset by peer。通常我们可能还会看到broken pipe错误,这是管道层面的错误,表示对已关闭的管道进行读写,往往是在收到RST,报出connection reset错后继续读写数据报的错,这个在glibc源码注释中也有介绍。

我们在排查故障时候怎么确定有RST包的存在呢?当然是使用tcpdump命令进行抓包,并使用wireshark进行简单分析了。tcpdump -i en0 tcp -w xxx.cap,en0表示监听的网卡。

img

接下来我们通过wireshark打开抓到的包,可能就能看到如下图所示,红色的就表示RST包了。

img


转载自:
https://blog.csdn.net/liuxiao723846/article/details/128402301

https://tech.kujiale.com/ying-yong-pin-fan-bao-chu-cause-java-net-sockettimeoutexception-read-timed-outzen-yao-ban/