Java Socket多线程文件传输:客户端上传后GUI卡顿问题
Java Socket文件传输客户端上传后GUI卡顿问题解决
问题根源
服务器处理上传逻辑时,通过while ((bytesRead = in.read(buffer)) != -1)循环读取文件内容,这个循环会一直阻塞,直到输入流被主动关闭。但客户端上传完成后并没有关闭Socket输出流,反而写入了一个out.writeLong(-1)——这个long类型数据会被服务器当作文件内容的一部分读取,服务器永远收不到流结束的-1信号,导致ClientHandler线程一直卡在上传读取循环中,无法响应客户端后续的list、download等命令。
客户端上传完成后调用listFiles(),此时客户端的in.readInt()会一直等待服务器返回数据,但服务器还卡在上传循环里,导致SwingWorker后台线程阻塞,所有依赖服务器响应的操作都无法执行,最终表现为GUI卡顿。
解决方案
1. 统一文件传输协议:先传文件大小,再传内容
通过先传递文件大小,服务器可以精确控制读取的字节数,无需依赖流结束信号,从根本上解决阻塞问题。
2. 移除客户端无效的结束标记
删除客户端上传后的out.writeLong(-1),避免向服务器发送错误数据。
3. 规范Swing组件操作(推荐)
所有更新Swing组件的操作必须在事件调度线程(EDT)中执行,避免线程安全问题,同时保证GUI响应流畅。
修改后的代码示例
客户端uploadFile方法修改
private static void uploadFile() { JFileChooser fileChooser = new JFileChooser(); int returnVal = fileChooser.showOpenDialog(null); if (returnVal == JFileChooser.APPROVE_OPTION) { File selectedFile = fileChooser.getSelectedFile(); try { out.writeUTF("upload"); out.writeUTF(selectedFile.getName()); // 先发送文件大小 out.writeLong(selectedFile.length()); try (BufferedInputStream fileIn = new BufferedInputStream(new FileInputStream(selectedFile))) { byte[] buffer = new byte[1024]; int bytesRead; long totalSent = 0; while (totalSent < selectedFile.length() && (bytesRead = fileIn.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); totalSent += bytesRead; } } // 在EDT中显示上传成功提示 SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(null, "文件上传成功")); // 异步更新文件列表,确保EDT安全 SwingUtilities.invokeLater(() -> { new SwingWorker<Void, Void>() { @Override protected Void doInBackground() throws Exception { listFiles(); return null; } }.execute(); }); } catch (IOException e) { e.printStackTrace(); SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(null, "上传失败:" + e.getMessage())); } } }
服务器端上传逻辑修改
else if (command.equals("upload")) { String fileName = in.readUTF(); // 读取文件大小 long fileSize = in.readLong(); try (OutputStream fileOut = new FileOutputStream(FILE_DIRECTORY + fileName)) { byte[] buffer = new byte[1024]; int bytesRead; long totalReceived = 0; while (totalReceived < fileSize) { // 计算剩余需要读取的字节数,避免过度读取 int readSize = (int) Math.min(buffer.length, fileSize - totalReceived); bytesRead = in.read(buffer, 0, readSize); if (bytesRead == -1) { // 连接意外中断,终止读取 break; } fileOut.write(buffer, 0, bytesRead); totalReceived += bytesRead; } } }
客户端listFiles方法EDT安全优化
private static void listFiles() { try { out.writeUTF("list"); int numFiles = in.readInt(); List<String> files = new ArrayList<>(); for (int i = 0; i < numFiles; i++) { String fileName = in.readUTF(); files.add(fileName); } // 在EDT中更新列表模型 SwingUtilities.invokeLater(() -> { listModel.clear(); files.forEach(listModel::addElement); }); } catch (IOException e) { e.printStackTrace(); SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(null, "获取文件列表失败:" + e.getMessage())); } }
内容的提问来源于stack exchange,提问作者Carlos Curcino
相关产品推荐
相关产品推荐

