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

如何正确使用writeUTF()方法?文件传输客户端服务端异常排查

问题根源:客户端与服务端的数据流读写顺序完全不匹配

嘿,这问题太典型了——你在客户端发送数据的顺序,和服务端接收数据的顺序完全对不上,直接导致数据流错位,所以文件名读不到,后续文件操作自然抛出FileNotFoundException。

我们先理清楚客户端PUT流程的发送顺序:

  • 第一步:发送操作类型字符 'P'
  • 第二步:发送文件名 writeUTF(FileOrDirName)
  • 第三步:发送文件/目录标识(0 代表文件,1 代表目录)
  • 第四步:如果是文件,先发送文件长度(你用了writeDouble,其实用writeInt更合理,因为字节数组长度本身就是int类型),再逐字节发送文件内容;如果是目录,直接发送压缩后的字节数组

再看你的服务端接收顺序:

  • 读取'P'之后,直接去读double类型的长度(这时候读的其实是文件名的UTF编码头,完全错误)
  • 接着读字节数组(这部分是被破坏的错误数据)
  • 最后才去读文件名的UTF数据——这时候数据流已经被前面的错误读取搅乱了,自然读不到有效内容,输出空行,创建文件时当然会报错

修正后的服务端代码(PUT核心逻辑)

我帮你调整了接收顺序,完全匹配客户端的发送逻辑,还优化了资源管理:

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.nio.file.Path;
import java.nio.file.Paths;

class TcpFileServerThread extends Thread {
    private Socket client;

    public TcpFileServerThread(Socket client) {
        this.client = client;
    }

    public void run() {
        // 用try-with-resources自动关闭流,避免资源泄漏
        try (DataOutputStream outToClient = new DataOutputStream(client.getOutputStream());
             DataInputStream dataInFromClient = new DataInputStream(client.getInputStream())) {
            Path currentRelativePath = Paths.get("");
            String currentPath = currentRelativePath.toAbsolutePath().toString();

            switch (dataInFromClient.readChar()) {
                case 'P':
                    // 第一步:先读取文件名,和客户端发送顺序对应
                    String fileOrDirName = dataInFromClient.readUTF();
                    System.out.println("收到的文件名/目录名:" + fileOrDirName);

                    // 第二步:读取文件/目录标识
                    int isDirFlag = dataInFromClient.readInt();
                    File targetFile = new File(fileOrDirName);

                    // 字符串比较要用equals,==是比较对象引用
                    if (targetFile.getParent() == null || currentPath.equals(targetFile.getParent())) {
                        if (isDirFlag == 0) {
                            // 处理文件:先读取长度,再读取字节内容
                            // 注意:客户端用了writeDouble,这里暂时对应readDouble,建议后续统一改成int
                            long fileLength = (long) dataInFromClient.readDouble();
                            byte[] fileBytes = new byte[(int) fileLength];
                            // 用readFully确保读取完整字节数组
                            dataInFromClient.readFully(fileBytes);
                            FileOperations.writeFromByteArrayToFile(fileBytes, fileOrDirName);
                        } else {
                            // 处理目录:先读取压缩包长度(建议客户端补充发送),再读取字节
                            // 临时方案:先读取当前可用字节,后续建议客户端先发送压缩包长度
                            byte[] zipBytes = new byte[dataInFromClient.available()];
                            dataInFromClient.readFully(zipBytes);
                            File zipFile = new File(fileOrDirName + ".zip");
                            FileOperations.writeFromByteArrayToFile(zipBytes, zipFile.getAbsolutePath());
                            FileOperations.unzip(zipFile);
                            zipFile.delete();
                        }
                    }
                    break;
                // 后续可以补充GET、EXIT的处理逻辑
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                client.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

额外优化建议

  1. 统一数据类型:文件长度用int就足够(字节数组最大长度为Integer.MAX_VALUE),没必要用double,避免类型转换的潜在问题。
  2. 补充长度发送:发送字节数组(比如压缩后的目录)时,建议先发送数组的长度,服务端根据长度精确读取,不要依赖available()(它只能返回当前可读取的字节数,不一定是全部)。
  3. 字符串比较规范:客户端里的fileOrDir.getParent()==currentPath要改成currentPath.equals(fileOrDir.getParent()),Java中==比较字符串是对比对象引用,不是内容。
  4. 完善异常处理:可以在关键步骤(比如文件读写、网络操作)添加更具体的异常提示,方便排查问题。

内容的提问来源于stack exchange,提问作者Dimitry

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 06:34:07