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ć
相关产品推荐
相关产品推荐

