RST及java socket关闭时的各种异常

RST

TCP/IP协议中,连接正常关闭时双方会发送FIN,经历4次挥手过程。此外,还可以通过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并忽略这些数据

此外,还有一种情况会出现RST:

A向B发送数据,B已经通过close()方法关闭了Socket,虽然TCP规定半关闭状态下B仍然可以接收数据,但close动作关闭了该socket上的任何数据操作,如果此时A继续write,B将返回RST,A的该次write无法立即通知应用层,只会把状态保存在tcp协议栈内,下次write时才会抛出SocketException。

Socket关闭时的异常

Socket关闭时会涉及各式各样的异常,以下总结了常见的几类:

主动关闭方

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

被动关闭方

收到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()关闭连接,己方还在写数据

以上情况都写了代码并结合wireshark抓包测试,比较简单就不贴出来了。

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

怎么避免意外的RST(及其带来的各种异常)?

针对几种出现RST的情况:

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

参考资料

Orderly Versus Abortive Connection Release in Java
UNIX网络编程——shutdown 与 close 函数 的区别
几种TCP连接中出现RST的情况

p.s     java程序员还是得熟悉底层理论以及Unix下的C编程。