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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 02:07:03