You need to enable JavaScript to run this app. 使用Java Socket结合Files.copy传输文件后调用readUTF阻塞问题求解
优惠活动
大模型
产品
解决方案
定价
更多

使用Java Socket结合Files.copy传输文件后调用readUTF阻塞问题求解

问题根因

  • Files.copy(InputStream source, Path target) 方法的默认行为是持续从输入流读取数据,直到读到流末尾(返回-1)才会终止,而Socket的输入流只有在对方关闭输出流、或者断开连接的时候才会返回流末尾标记。
  • 你当前的逻辑出现了死锁:
    1. 服务端执行到Files.copy(dataInputStream, Paths.get(fileName))后,一直在等客户端发送更多数据或者关闭输出流,不会往下执行发送响应的代码
    2. 客户端发完文件后没有关闭输出流,直接执行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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.11 22:00:02