问题背景:客户端连接服务器,发送一个请求,捕获响应信息。

// 建立连接
Socket socket = new Socket();
socket.connect(new InetSocketAddress(InetAddress.getLocalHost(),9999),10000);

// 将输入、输出流包装成数据输入、输出流
DataInputStream dis2 = new DataInputStream(socket.getInputStream());
DataOutputStream dos2 = new DataOutputStream(socket.getOutputStream());

// 写入数据
GetCommand get2 = new GetCommand("zhangsan", "");
dos2.writeUTF("GET");
dos2.write(SerializeUtil.serialize(get2));

dos2.close();

// 接受响应
GetCommand deserialize2 = SerializeUtil.deserialize(dis2.readAllBytes(), GetCommand.class);
System.out.println(deserialize2);

// 关闭流 和 socket
dis2.close();
socket.close();

客户端通过输出流向服务器发送数据, 如果不关闭输出流,服务器无法判断出客户端是否已经输出完毕,因此服务器的读操作将会处于阻塞状态。

当客户端关闭输出流,服务器得到客户端的输出已经结束的信息,服务器开始执行读操作。

然而,这会导致另外一个问题,客户端输出流关闭的时候,socket 也会自动断开连接。当服务器需要通过输出流向客户端传输数据时,便会出现如同标题的异常。

解决方案:使用 半关闭。

// dos2.close(); // 会导致 socket 连接断开

socket.shutdownOutput();
// 现在 socket 是半关闭状态,输出流关闭,但输入流打开,socket 连接不会断开。

以下内容摘自《Java 核心技术卷2》第三章

2.2 半关闭

套接字连接的一端可以终止其输出,同时仍旧可以接受来自另一端的数据。

这是一种很典型的情况,例如我们在向服务器传输数据,但并不知道要传输多少个数据。如果关闭一个套接字,那么服务器的连接将立刻断开,因而也就无法读取服务器的响应了。

使用半关闭的方法就可以解决上述的问题,可以通过关闭一个套接字的输出流来表示发送给服务器的请求数据已经结束,但是必须保持输入流处于打开状态。

socket.shutdownOutput();
socket.shutdownInput();

当然,该协议只适用于一站式的服务,例如 HTTP 服务,在这种服务中,客户端连接服务器,发送一个请求,捕获响应信息,然后断开连接。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐