基于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
相关产品推荐
相关产品推荐

