Socket编程问题:客户端无法实时接收服务器推送消息
游戏Socket编程消息阻塞问题分析与修复
问题背景
服务器每0.2秒(一个游戏tick)会通过sendGameWorldSections()方法向已连接客户端发送游戏世界数据,该操作单独开线程执行以避免阻塞游戏主循环。但客户端无法实时接收消息,仅在关闭客户端时才会批量显示所有积压的消息,需排查问题根源。
核心问题定位
1. 服务器发送端:缺少换行符+未触发流刷新
服务器sendMessageToPlayer方法存在关键问题:
public static void sendMessageToPlayer(String playerID, String message) throws IOException { System.out.println("Sending message.."); Socket socket = playerIDToSocket.get(playerID); PrintWriter out = new PrintWriter(socket.getOutputStream(), true); out.print(message); }
- 客户端使用
in.readLine()接收消息,该方法会阻塞直到读取到换行符\n或流关闭,但服务器仅发送JSON字符串,未添加换行符; - 虽然
PrintWriter构造时开启了autoFlush,但该特性仅在调用println()/printf()/format()时生效,print()不会触发自动刷新,导致消息一直积压在输出缓冲区,直到缓冲区满或连接关闭才会发送。
2. 客户端接收端:单次读取逻辑缺陷
客户端listen方法仅调用一次in.readLine(),若服务器未发送换行符,该方法会一直阻塞,直到流关闭时才会一次性读取所有缓冲数据,这就是关闭客户端才批量显示消息的直接原因。
修复方案
服务器端修改(核心修复)
修改sendMessageToPlayer方法,使用println()自动添加换行符并触发刷新:
public static void sendMessageToPlayer(String playerID, String message) throws IOException { System.out.println("Sending message.."); Socket socket = playerIDToSocket.get(playerID); PrintWriter out = new PrintWriter(socket.getOutputStream(), true); out.println(message); // 自动添加换行符,同时触发autoFlush }
若坚持使用print(),需手动添加换行并强制刷新:
out.print(message + "\n"); out.flush();
客户端优化(可选)
调整监听逻辑为持续读取模式,确保能实时处理每条消息:
public void listen(BufferedReader in){ String message; try { // 持续读取直到流关闭 while ((message = in.readLine()) != null) { System.out.println("[SERVER]: " + message); if(isJSONValid(message)){ // 原有JSON解析与游戏逻辑处理 } } } catch (IOException e) { throw new RuntimeException(e); } catch (JSONException e) { throw new RuntimeException(e); } }
额外优化建议
- 静态
playerIDToSocket未做并发同步,多线程访问可能出现异常,建议替换为ConcurrentHashMap或添加同步锁; - GameTicker每次tick创建新线程的方式存在性能开销,建议使用线程池管理发送任务。
内容的提问来源于stack exchange,提问作者Andrew Kor
相关产品推荐
相关产品推荐

