协议里常需要「我说完了,但还等你的回应」。TCP 半关闭让一端结束发送、仍然接收。这篇用 Socket、SocketChannel 与真实的 lsof 状态,逐项验证 shutdownOutput 与 shutdownInput 的行为,以及 close 发出 FIN 与 RST 的差别。
客户端 shutdownOutput() 之后:isOutputShutdown = true,isInputShutdown = false,isClosed = false,isConnected = true shutdownOutput 之后再写 → SocketException: Socket output is shutdown 客户端仍然可以读:响应:收到 5 个字符的请求 服务器读到 EOF 才开始响应,请求内容「hello」 对照:客户端发完请求直接 close() 而不是 shutdownOutput():服务器写响应成功(没有立刻报错);服务器写 → SocketException: Broken pipe
连接建立后:客户端 ESTABLISHED; 服务器端 ESTABLISHED 客户端 shutdownOutput() 之后(服务器还没有 close):客户端 FIN_WAIT_2; 服务器端 CLOSE_WAIT 服务器端 read 得到 -1(-1 = EOF) 服务器 close() 之后:客户端 TIME_WAIT 客户端也 close() 之后:(lsof 看不到:没有进程持有的连接,例如 TIME_WAIT)
SocketChannel shutdownOutput 之后 write → AsynchronousCloseException 客户端 SocketChannel 仍然可以读:3 字节「bye」;再读一次 = -1 服务器 read:第 1 次 2 字节,第 2 次(对端 shutdownOutput 后)返回 -1(-1 = EOF) shutdownInput() 之后 read → -1(立刻返回 -1) 对端没发任何东西时 select(200):就绪 key 数 0 对端 shutdownOutput() 之后:select 返回就绪 key 数 1,isReadable = true;read 返回 -1(OP_READ 就绪表示读到 EOF) 不处理这个 EOF、继续 select:就绪 key 数 1(EOF 是水平触发的,一直就绪,要处理掉或取消 OP_READ)
客户端 setSoLinger(true, 0) 后 close()(发 RST):服务器读:第 1 次 17 字节,第 2 次 → SocketException: Connection reset 客户端默认 close()(发 FIN):服务器读:第 1 次 17 字节,第 2 次 -1(-1 = 正常 EOF) shutdownInput 之后再 getInputStream() → SocketException: Socket input is shutdown 客户端 shutdownInput() 之前服务器已经发来数据,之后用事先取得的 InputStream.read → -1(-1:读不到);服务器后续 shutdownOutput:服务器 shutdownOutput 成功