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

Java文件传输应用中DataInputStream无法识别输入的问题

问题原因分析与解决方法

问题原因分析

1. 非线程安全的HashMap引发并发状态不一致

GreenServer使用HashMap<String, Client>存储在线客户端,但HashMap不支持并发操作。多个ServerHandler线程同时执行put、remove、containsKey操作时会出现竞态条件:

  • proxy()方法中while (clients.containsKey(thisClient))检查通过后,另一个线程可能已执行removeClient将该客户端从Map中移除,导致当前线程继续操作已失效的流;
  • Map内部结构可能因并发操作损坏,导致containsKey返回错误结果,线程陷入无意义的循环尝试读取无效流。

2. SocketException处理不完整,导致流处于无效状态

在sendFiles()方法的SocketException捕获块中:

  • 仅处理接收方的连接关闭,未检查或处理当前客户端(发送方)的输入流状态。当SocketException由发送方输入流读取失败引发时,DataInputStream会被标记为无效状态,后续readUTF()会出现:
    • 阻塞等待完整的UTF-8长度前缀(因之前的异常导致缓存数据不完整);
    • 静默读取错误数据或无法解析客户端请求,导致Wireshark能看到发送的请求但服务器无法识别;
  • 捕获块执行完成后直接返回sendFiles(),回到proxy()的while循环继续读取无效流,问题持续存在。

3. SocketException捕获逻辑的场景错误

注释说明该捕获块用于处理接收方断开连接,但SocketException可能由当前线程(发送方)的输入流异常触发,此时操作接收方连接(clients.get(receiver).closeConnection())属于错误逻辑,还可能引发空指针异常(若receiver已被移除)。


解决方法

1. 替换HashMap为线程安全的ConcurrentHashMap

将GreenServer中的HashMap替换为ConcurrentHashMap,避免并发操作导致的状态不一致:

// 原代码
HashMap<String, Client> clients = new HashMap<>();
// 修改为
ConcurrentHashMap<String, Client> clients = new ConcurrentHashMap<>();

同时优化proxy()方法的循环判断,避免竞态:

// 原代码
while (clients.containsKey(thisClient)) {
    // ...
}
// 修改为
while (clients.get(thisClient) != null) {
    // ...
}

2. 完善SocketException的处理逻辑

在sendFiles()的SocketException捕获块中,优先处理当前客户端的连接状态,确保线程正常退出:

catch (SocketException e) {
    System.out.println("Lost connection for user: " + thisClient);
    // 处理当前客户端
    Client currentClient = clients.get(thisClient);
    if (currentClient != null) {
        currentClient.setOccupied(false);
        try {
            dos.writeUTF("###");
            dos.flush();
        } catch (IOException ignore) {}
        removeClient(thisClient);
    }
    // 处理接收方(若存在)
    Client receiverClient = clients.get(receiver);
    if (receiverClient != null) {
        receiverClient.setOccupied(false);
        try {
            receiverClient.getDos().writeUTF("###");
            receiverClient.getDos().flush();
            receiverClient.closeConnection();
        } catch (IOException ignore) {}
        clients.remove(receiver);
    }
    // 终止当前线程的请求处理
    return;
}

关键修改:

  • 操作前检查客户端实例是否存在,避免空指针;
  • 处理完成后调用removeClient关闭流并移除客户端,确保while循环退出;
  • 直接return终止后续处理,不再尝试读取无效流。

3. 统一异常处理逻辑

在proxy()方法的while循环中,增加通用IOException捕获,避免未处理的异常导致线程静默终止:

try {
    String x = dis.readUTF();
    // ... 请求处理逻辑
} catch (SocketTimeoutException e) {
    System.out.println("Connection has been closed by server...");
    removeClient(thisClient);
} catch (IOException e) {
    System.out.println("IO error for client " + thisClient + ": " + e.getMessage());
    removeClient(thisClient);
}

内容的提问来源于stack exchange,提问作者Jovan Vuković

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 21:27:04