使用Java Socket结合Files.copy传输文件后调用readUTF阻塞问题求解
问题根因
Files.copy(InputStream source, Path target)方法的默认行为是持续从输入流读取数据,直到读到流末尾(返回-1)才会终止,而Socket的输入流只有在对方关闭输出流、或者断开连接的时候才会返回流末尾标记。- 你当前的逻辑出现了死锁:
- 服务端执行到
Files.copy(dataInputStream, Paths.get(fileName))后,一直在等客户端发送更多数据或者关闭输出流,不会往下执行发送响应的代码 - 客户端发完文件后没有关闭输出流,直接执行
readUTF()等待服务端返回响应,两边都在等对方操作,所以双双阻塞
- 服务端执行到
- 你删掉客户端的
readUTF()后,客户端发完文件就会退出try-with-resources代码块,Socket会被自动关闭,服务端的输入流读到EOF,Files.copy就能正常执行完毕,所以看起来程序正常运行,但此时服务端发送的响应已经没有接收方了。
解决方案
有两种常用的方案解决这个问题:
方案1:客户端发送完文件后半关闭输出流
客户端发送完文件后,调用shutdownOutput()关闭自己的输出流,此时服务端的输入流就能读到流末尾,Files.copy正常结束后就会发送响应,且客户端的输入流仍然处于打开状态,可以正常读取响应:
修改后的客户端代码:
public static void main(String[] args) throws IOException { String fileName = "hello.txt"; try (Socket clientSocket = new Socket(HOST, PORT)) { DataInputStream dataInputStream = new DataInputStream(clientSocket.getInputStream()); DataOutputStream dataOutputStream = new DataOutputStream(clientSocket.getOutputStream()); dataOutputStream.writeUTF(fileName); Files.copy(Paths.get(fileName), dataOutputStream); // 新增这行,半关闭输出流,告知服务端文件已发送完毕 clientSocket.shutdownOutput(); String response = dataInputStream.readUTF(); LOGGER.info(response); } }
这个方案实现简单,适合单文件传输的场景,缺点是半关闭后客户端不能再往服务端发送任何数据,只适合单次请求单次响应的逻辑。
方案2:提前发送文件长度,服务端按长度读取
在发送文件名之后,先发送文件的字节长度,服务端读取到长度后,只读取对应长度的字节作为文件内容,不需要等待流末尾:
修改后的客户端代码:
public static void main(String[] args) throws IOException { String fileName = "hello.txt"; File file = new File(fileName); try (Socket clientSocket = new Socket(HOST, PORT)) { DataInputStream dataInputStream = new DataInputStream(clientSocket.getInputStream()); DataOutputStream dataOutputStream = new DataOutputStream(clientSocket.getOutputStream()); dataOutputStream.writeUTF(fileName); // 先发送文件长度 dataOutputStream.writeLong(file.length()); Files.copy(Paths.get(fileName), dataOutputStream); String response = dataInputStream.readUTF(); LOGGER.info(response); } }
修改后的服务端代码:
public static void main(String args[]) throws IOException { try (ServerSocket ss = new ServerSocket(PORT)) { Socket clientSocket = ss.accept(); DataOutputStream dataOutputStream = new DataOutputStream(clientSocket.getOutputStream()); DataInputStream dataInputStream = new DataInputStream(clientSocket.getInputStream()); String fileName = dataInputStream.readUTF(); // 读取文件长度 long fileLength = dataInputStream.readLong(); // 只读取指定长度的字节写入文件 try (OutputStream fos = new FileOutputStream(fileName)) { byte[] buffer = new byte[8192]; long remaining = fileLength; int read; while (remaining > 0 && (read = dataInputStream.read(buffer, 0, (int)Math.min(buffer.length, remaining))) != -1) { fos.write(buffer, 0, read); remaining -= read; } } dataOutputStream.writeUTF("New file saved"); } }
这个方案更灵活,适合后续需要扩展多文件传输、多次交互的场景。
内容的提问来源于stack exchange,提问作者Anthonny Giroud
相关产品推荐
相关产品推荐

