Java客户端-服务端文件传输程序死锁排查与实现方案咨询
问题根因
你遇到的不是传统多线程锁导致的死锁,是TCP字节流无边界、两端IO阻塞等待条件不匹配导致的双向等待:
- 服务端接收文件的循环逻辑是持续调用
read()直到返回-1,而TCP流的read()只有在对端主动关闭输出流/断开连接时才会返回-1,只要连接保持、对端没关流,服务端就会一直阻塞在read调用上,根本走不到后面返回"success"响应的逻辑。 - 你注释掉客户端读响应的代码时,程序最后会执行
clientSocketOutputWriter.close()关闭输出流,服务端read拿到-1就会继续向下执行,所以看起来运行正常;但你放开读响应的代码后,客户端发完文件内容就阻塞在readLine()等服务端返回响应,服务端阻塞在read()等客户端关流,两边互相等就卡死了。
另外你现有服务端读逻辑还有个隐蔽bug:do-while循环第一次执行时会先把未赋值的空mass数组写入字节输出流,再去读数据,最终保存的文件开头会多一个无效的0字节;而且1字节的缓冲区读文件效率极低,完全没必要。
修复步骤
先解决当前的双向阻塞问题
最小改动就能让现有单文件传输+响应的逻辑跑通:
- 客户端发完文件内容后,不要直接关闭整个Socket,调用
client.shutdownOutput()单向关闭输出流,明确告诉服务端"当前文件内容已经发完,不会再有写入数据",服务端read此时就能拿到-1,继续执行后续逻辑。 - 服务端写"success"响应时,末尾要加换行符,写完立刻调用
flush()把缓冲区数据推出去,不然客户端的readLine()会因为读不到行结束标记一直阻塞。 - 修正服务端读流的顺序:先读取数据,判断读到有效字节数后再写入输出流,避免写入无效空字节,同时把缓冲区调整为4KB以上提升读写效率。
- 所有流、Socket资源改用try-with-resources语法自动关闭,不要手动写close逻辑,避免中途抛异常导致资源泄漏。
实现多文件传输、文件变更自动同步的合理方案
TCP本身是无边界的字节流,不会自动区分你发的内容属于第几个文件、从哪开始到哪结束,要实现长期运行、多文件传输的需求,必须自己定义简单的应用层传输边界,不要靠关流判断单文件传输结束,推荐的传输帧结构:
- 2字节:存储后续文件名的字节长度
- N字节(长度和前面的2字节值一致):文件名的UTF-8编码字节
- 8字节(long类型):存储后续文件内容的总长度
- M字节(长度和前面的8字节值一致):文件的实际内容字节
- 1字节:服务端接收完整个文件后返回的状态标记(0代表成功,1代表失败)
按这个结构传输时,服务端每次读数据都知道自己应该读多少字节,读完立刻返回响应,不需要等连接关闭,同一个长连接可以反复传输多个文件,不会出现阻塞等待的问题。
文件变更检测直接用JDK自带的java.nio.file.WatchService实现即可,不需要自己轮询对比文件修改时间:
- 监听目标文件所在目录的
ENTRY_MODIFY事件 - 加500ms左右的防抖逻辑:大部分编辑器保存文件时会连续触发多次modify事件,收到第一次事件后等待500ms没有新的同文件事件触发,再读取文件内容发送,避免传输写入到一半的损坏文件。
- 客户端启动时和服务端建立一个长连接复用,不要每次传文件都新建Socket,降低连接开销。
修正后的核心代码片段
服务端核心接收逻辑
// 用try-with-resources自动释放资源 public class Server { public static void main(String[] args) throws IOException { try (ServerSocket serverSocket = new ServerSocket(5000); Socket client = serverSocket.accept(); BufferedInputStream clientInputStream = new BufferedInputStream(client.getInputStream()); BufferedWriter responseWriter = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()))) { byte[] buffer = new byte[4096]; ByteArrayOutputStream fileContentBuffer = new ByteArrayOutputStream(); int bytesRead; // 修正读流顺序:先读再写,直到拿到流结束标记 while ((bytesRead = clientInputStream.read(buffer)) != -1) { fileContentBuffer.write(buffer, 0, bytesRead); } // 写文件 try (FileOutputStream fos = new FileOutputStream("this.txt"); BufferedOutputStream bos = new BufferedOutputStream(fos)) { bos.write(fileContentBuffer.toByteArray()); } System.out.println("first file received"); // 返回响应,加换行+flush responseWriter.write("success\n"); responseWriter.flush(); } } }
客户端核心发送逻辑
public class Client { private final static String serverIP = "192.168.0.47"; private final static int serverPort = 5000; private final static String fileOutput = "first.txt"; public static void main(String[] args) throws IOException { try (Socket client = new Socket(serverIP, serverPort); BufferedInputStream inputFile = new BufferedInputStream(new FileInputStream(fileOutput)); BufferedOutputStream clientSocketOutputWriter = new BufferedOutputStream(client.getOutputStream()); BufferedReader responseReader = new BufferedReader(new InputStreamReader(client.getInputStream()))) { byte[] fileBytes = inputFile.readAllBytes(); clientSocketOutputWriter.write(fileBytes); // 发完文件后flush缓冲区,再单向关输出流通知服务端发送结束 clientSocketOutputWriter.flush(); client.shutdownOutput(); System.out.println("first file sent"); // 读服务端响应 String resp = responseReader.readLine(); System.out.println("server response: " + resp); System.out.println("the end"); } } }
内容的提问来源于stack exchange,提问作者life_fire1
相关产品推荐
相关产品推荐

