Java Socket服务端向客户端传输文件出现死循环及文件损坏问题求解
问题根源分析
- 缺少明确的传输边界:你的需求是保持Socket长连接支持多次双向传输,所以传输完文件不会关闭Socket,此时
InputStream.read()方法永远不会返回-1,客户端的while循环会一直阻塞/死循环,无法正常结束文件写入,最终导致文件损坏。 - 数据读取顺序错误:服务端传输文件前调用了
writeUTF()发送状态字符串,客户端没有先读取该字符串,直接把状态字符串的二进制内容写入了目标文件,直接造成文件内容异常。 - 代码逻辑缺陷:
- 客户端声明了
BufferedOutputStream outBuffer但未实际使用,缓冲区优化无效 - 不必要的5秒线程休眠,既影响性能也可能引发流数据读取时序问题
- 服务端文件不存在的异常分支中,输出错误提示后未终止流程,后续会尝试读取null的文件对象触发空指针异常,导致传输逻辑混乱
- 客户端声明了
修复方案
核心思路是在不关闭Socket的前提下,通过提前传输文件元数据(文件大小)明确传输边界,避免依赖流关闭作为传输结束标志。
服务端代码修改
try { String path = currentPath + "\\" + name; File fichierAEnvoyer = new File(path); // 先判断文件是否存在、是否是正常文件 if (!fichierAEnvoyer.exists() || !fichierAEnvoyer.isFile()) { out.writeUTF("ERROR:Le fichier n'existe pas! Veuillez entrer son nom de nouveau!"); return; // 异常直接终止后续流程 } // 发送正常传输状态 out.writeUTF("SUCCESS:Tentative de televersement du serveur au client."); // 先发送文件总大小,作为传输边界 out.writeLong(fichierAEnvoyer.length()); final short TAILLE_OCTET_MAXIMUM = 1024; byte[] buffer = new byte[TAILLE_OCTET_MAXIMUM]; int count; OutputStream outputStream = socket.getOutputStream(); BufferedInputStream input = new BufferedInputStream(new FileInputStream(fichierAEnvoyer)); while ((count = input.read(buffer)) >= 0) { outputStream.write(buffer, 0, count); } outputStream.flush(); // 全部写完后统一刷缓冲区,不用每次循环刷 input.close(); } catch(Exception e) { out.writeUTF("ERROR:Une erreur est survenue lors du televersement du serveur au client."); e.printStackTrace(); }
客户端代码修改
try { out.writeUTF(entree); // 移除不必要的5秒休眠 // 先读取服务端返回的状态信息 String status = in.readUTF(); // 这里的in是和服务端out对应的DataInputStream if (status.startsWith("ERROR")) { System.out.println("服务端返回错误:" + status.substring(6)); return; } // 读取文件总大小 long fileSize = in.readLong(); long totalRead = 0; // 累计已读取的字节数 FileOutputStream fos = new FileOutputStream(new File(uploadPath + "\\" + name)); byte[] buffer = new byte[1024]; int count; InputStream inputStream = socket.getInputStream(); // 按实际文件大小控制读取结束时机 while(totalRead < fileSize && (count = inputStream.read(buffer, 0, (int)Math.min(buffer.length, fileSize - totalRead))) >= 0){ fos.write(buffer, 0, count); totalRead += count; } fos.close(); System.out.println("Tentative de telechargement du serveur reussie!"); } catch(Exception e) { System.out.println("Une erreur lors de la reception du serveur est survenue..."); e.printStackTrace(); }
长连接下文件传输的规范实现要点
- 每次传输前先发送固定结构的元数据,包括但不限于文件大小、文件名、校验值(MD5/SHA1)等,接收方先解析元数据再接收文件内容,传输完成后用校验值验证文件完整性
- 为Socket的输入输出操作设置超时时间,避免网络异常时出现无限阻塞
- 使用try-with-resources语法自动管理文件流等本地资源,避免资源泄漏
- 可以设计简单的指令协议区分不同的传输操作(上传/下载/消息通信),适配双向多轮传输的需求
内容的提问来源于stack exchange,提问作者Artour Benevolenski
相关产品推荐
相关产品推荐

