You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 20:05:30