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

基于Java Socket的跨设备文件传输异常问题求助

Java Socket文件跨局域网传输损坏问题排查与修复

问题场景

基于Java Socket开发的文件传输应用,本地回环地址(127.0.0.1)测试时文件传输完整可读,但同一局域网两台设备间传输时,接收文件大小接近原文件两倍且损坏。接收端已通过判断读取字节数等于原文件大小终止循环,程序输出“Done reading”提示读取完成,但问题依旧。

核心实现逻辑

1. 文件元信息传输类(FileTransferMessage)

public class FileTransferMessage extends Message {

private final String fileName;
private final long fileSize;
private final int chunkSize;
private final PeerAddress transferAddress;

public FileTransferMessage(String senderID, PeerAddress transferAddress, String fileName, int chunkSize, long fileSize) {
    super(MessageType.FileTransferMessage, senderID);
    this.fileName = fileName;
    this.transferAddress = transferAddress;
    this.fileSize = fileSize;
    this.chunkSize = chunkSize;
}

2. 发送端文件流线程(SendingStreamThread)

public class SendingStreamThread extends Thread {

private final String filePath;
private final ServerSocket serverSocket;
private final int batchSize;
private long remainingBatches;

public SendingStreamThread(Peer ownerPeer, String filePath, int batchSize, long fileSize) throws IOException {
    this.filePath = filePath;
    this.serverSocket = new ServerSocket(0, 1, InetAddress.getByName(ownerPeer.getThisPeerAddress().IPAddress()));
    this.remainingBatches = fileSize % batchSize == 0 ? fileSize / batchSize : fileSize / batchSize + 1;
    this.batchSize = batchSize;
}

@Override
public void run() {
    try (FileInputStream fileInputStream = new FileInputStream(filePath)) {
        Socket socket = serverSocket.accept();
        while (remainingBatches > 0) {
            byte[] bytes = fileInputStream.readNBytes(batchSize);
            socket.getOutputStream().write(bytes);
            remainingBatches--;
        }
        fileInputStream.close();
        socket.close();
        serverSocket.close();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

public PeerAddress getServerSocketPort() {
    return new PeerAddress(serverSocket.getInetAddress().getHostAddress(), serverSocket.getLocalPort());
}

3. 接收端文件流线程(ReceivingStreamThread)

public class ReceivingStreamThread extends Thread {

private final int chunkSize;
private final String finalPath;
private final PeerAddress source;
private final long fileSize;

public ReceivingStreamThread(FileTransferMessage fileTransferMessage) throws IOException {
    this.chunkSize = fileTransferMessage.getChunkSize();
    this.source = fileTransferMessage.getTransferAddress();
    this.fileSize = fileTransferMessage.getFileSize();
    String filePath = System.getProperty("user.home") + "\\Desktop\\";
    File file = new File(filePath + fileTransferMessage.getFileName());
    String extension = FilenameUtils.getExtension(fileTransferMessage.getFileName());
    String baseName = FilenameUtils.getBaseName(fileTransferMessage.getFileName());
    int i = 1;
    while (file.exists()) {
        baseName = FilenameUtils.getBaseName(fileTransferMessage.getFileName());
        baseName += " (" + i + ")";
        file = new File(filePath + baseName + "." + extension);
        i++;
    }
    finalPath = filePath + baseName + "." + extension + ".tempst";
    file = new File(finalPath);
    if (!file.createNewFile()) {
        if (!file.delete())
            throw new RuntimeException();
        else file.createNewFile();
    }
}

@Override
public void run() {
    try {
        Socket socket = new Socket(source.IPAddress(), source.port());
        FileOutputStream output = new FileOutputStream(finalPath);
        byte[] bytes = new byte[chunkSize];
        long readBytes = 0;
        while (readBytes != fileSize) {
            readBytes += socket.getInputStream().read(bytes);
            output.write(bytes);
        }
        System.out.println("Done reading");
        output.close();
        finalizeFile();
    } catch (IOException e) {
        File file = new File(finalPath);
        if (file.delete())
            throw new RuntimeException(e);
    }
}

private void finalizeFile() throws IOException {
    File file = new File(finalPath);
    File file1 = new File(FilenameUtils.getFullPath(finalPath) + FilenameUtils.getBaseName(finalPath));
    if (!file.renameTo(file1)) {
        throw new RuntimeException();
    }
}

问题根源分析

核心问题出在接收端的字节读取与写入逻辑:

  • socket.getInputStream().read(bytes) 方法返回实际读取到的字节数,网络传输时数据包可能拆分,最后一次读取也大概率不会填满整个bytes数组,但本地回环传输延迟极低,read()几乎每次都能填满数组,因此问题未暴露。
  • 当前代码中,无论实际读取多少字节,都将整个bytes数组写入文件,导致未填满的数组中残留的上一次数据被重复写入,最终文件体积膨胀且数据损坏。

修复方案

修改接收端run()方法,根据实际读取的字节数写入文件:

@Override
public void run() {
    try (Socket socket = new Socket(source.IPAddress(), source.port());
         FileOutputStream output = new FileOutputStream(finalPath)) {
        byte[] bytes = new byte[chunkSize];
        long readBytes = 0;
        int actualRead;
        while (readBytes < fileSize) {
            actualRead = socket.getInputStream().read(bytes);
            if (actualRead == -1) {
                throw new IOException("Connection closed before receiving full file");
            }
            // 仅写入实际读取到的字节
            output.write(bytes, 0, actualRead);
            readBytes += actualRead;
        }
        System.out.println("Done reading");
        finalizeFile();
    } catch (IOException e) {
        File file = new File(finalPath);
        file.delete();
        throw new RuntimeException(e);
    }
}

同时优化发送端代码,使用try-with-resources自动关闭资源,避免手动关闭遗漏:

@Override
public void run() {
    try (FileInputStream fileInputStream = new FileInputStream(filePath);
         ServerSocket serverSocket = this.serverSocket;
         Socket socket = serverSocket.accept()) {
        while (remainingBatches > 0) {
            byte[] bytes = fileInputStream.readNBytes(batchSize);
            socket.getOutputStream().write(bytes);
            remainingBatches--;
        }
        // 明确告知接收端数据发送完毕
        socket.shutdownOutput();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

额外优化建议

  • 发送端发送完数据后调用socket.shutdownOutput(),明确告知接收端传输结束,避免接收端等待额外数据。
  • 增加文件校验机制(如MD5哈希),传输完成后验证文件完整性。
  • 完善异常处理逻辑,确保网络中断时能清理临时文件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 05:15:05