Java Socket BIO+Object流文件传输阻塞问题求助
Socket BIO文件传输阻塞问题解决
问题根源
服务端当前的读取逻辑依赖输入流的EOF标记终止循环,但客户端发送完文件内容后,socket输出流仍处于打开状态(try-with-resources会等到整个代码块结束才关闭),导致服务端ois.read(BUFFER)一直阻塞等待更多数据;同时客户端阻塞在ois.readObject()等待服务端响应,形成双向阻塞。
解决方案
方案一:客户端半关闭socket输出流
发送完文件后,调用socket.shutdownOutput()告知服务端数据已发送完毕,服务端会收到EOF,read方法返回-1,跳出循环继续执行后续逻辑。
修改后的客户端代码:
public class FileTransClient { private static final int BUFFER_SIZE = 1024 * 8; private static final byte[] BUFFER = new byte[BUFFER_SIZE]; public static void main(String[] args) { try (Socket socket = new Socket("localhost", 1888); ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream())) { File file = new File("D:\\trans\\1.mp4"); FileTransModel fileTransModel = new FileTransModel(); fileTransModel.setFileName(file.getName()); fileTransModel.setFileLength(file.length()); oos.writeObject(fileTransModel); oos.flush(); // 确保文件元数据立即发送 FileInputStream fileInputStream = new FileInputStream(file); BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream); int len; while ((len = bufferedInputStream.read(BUFFER)) != -1) { System.out.println(len); oos.write(BUFFER, 0, len); oos.flush(); } System.out.println("file send over"); // 半关闭输出流,通知服务端数据传输完成 socket.shutdownOutput(); FileTransModel model = (FileTransModel) ois.readObject(); System.out.println(model); } catch (IOException | ClassNotFoundException e) { throw new RuntimeException(e); } } }
方案二:服务端按文件长度精确读取(推荐)
依赖文件元数据中的fileLength,精确读取对应字节数,读完即终止循环,无需依赖EOF标记,更适合需要保持连接的场景。
修改后的服务端Task类代码:
private static class Task implements Runnable { Socket socket; public Task(Socket socket) { this.socket = socket; } @Override public void run() { try { ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream()); ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); FileTransModel fileTransModel = (FileTransModel) ois.readObject(); System.out.println(fileTransModel); File file = new File("D:\\trans_rec2\\"+fileTransModel.getFileName()); if (!file.exists()) { file.createNewFile(); } FileOutputStream fos = new FileOutputStream(file); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fos); long remainingBytes = fileTransModel.getFileLength(); int len; // 按文件长度精确读取,避免阻塞 while (remainingBytes > 0) { // 每次读取不超过缓冲区大小,也不超过剩余字节数 len = ois.read(BUFFER, 0, (int) Math.min(BUFFER_SIZE, remainingBytes)); if (len == -1) { throw new IOException("连接意外中断,文件未传输完成"); } bufferedOutputStream.write(BUFFER, 0, len); bufferedOutputStream.flush(); remainingBytes -= len; System.out.println(len + ": " + (fileTransModel.getFileLength() - remainingBytes)); } System.out.println("file write over"); fileTransModel.setStatus(0); oos.writeObject(fileTransModel); oos.flush(); // 确保响应立即发送 System.out.println("file receive over"); // 手动关闭资源 bufferedOutputStream.close(); fos.close(); ois.close(); oos.close(); socket.close(); } catch (IOException | ClassNotFoundException e) { throw new RuntimeException(e); } } }
额外注意事项
- 发送对象后调用
flush(),避免缓冲区延迟导致的交互阻塞。 - 妥善处理资源关闭,防止资源泄漏。
- 方案二无需修改客户端代码,兼容性更强,适合需要复用socket连接的场景。
内容的提问来源于stack exchange,提问作者Krlin Homon
相关产品推荐
相关产品推荐

