Java Socket双客户端石头剪刀布游戏:单客户端无法接收服务器消息
问题:Java Socket双客户端石头剪刀布服务端,单一客户端无法接收消息
我正在尝试用Java Socket实现一个支持两个客户端的石头剪刀布游戏服务端,但遇到了一个棘手的问题:其中一个客户端完全接收不到服务器发送的消息。以下是我的服务端代码片段:
package exam; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Scanner; public class Server_Game { public static void main(String[] args) throws Exception { // 这里是我的主逻辑代码,目前可能没有为每个客户端开独立线程 } }
问题分析与解决方案
从你的代码片段来看,最可能的问题有两个核心点:
- 主线程被单个客户端的IO操作阻塞:如果你的服务端在
accept客户端后,直接在主线程里处理该客户端的读写,那么当第一个客户端的IO操作(比如等待输入)阻塞主线程时,第二个客户端的消息发送逻辑根本无法执行。 - 输出流未及时flush:
DataOutputStream默认会缓冲数据,如果写完消息后没有调用flush(),消息会滞留在缓冲区,客户端永远收不到。
下面是修复后的完整服务端实现,针对这两个问题做了优化:
package exam; import java.io.DataInputStream; import java.io.DataOutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.CopyOnWriteArrayList; public class Server_Game { // 线程安全的集合存储客户端处理器,避免并发修改问题 private static CopyOnWriteArrayList<ClientHandler> clientHandlers = new CopyOnWriteArrayList<>(); // 共享状态:记录两个玩家的选择,用同步块保证线程安全 private static String[] playerChoices = new String[2]; private static int choiceCount = 0; public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(8888); System.out.println("石头剪刀布服务端已启动,等待玩家连接..."); // 循环接收客户端连接 while (true) { Socket clientSocket = serverSocket.accept(); int playerId = clientHandlers.size(); System.out.println("玩家" + (playerId + 1) + "已连接:" + clientSocket.getInetAddress()); // 为每个客户端创建独立的处理线程 ClientHandler handler = new ClientHandler(clientSocket, playerId); clientHandlers.add(handler); new Thread(handler).start(); } } // 内部类:负责单个客户端的IO处理和游戏逻辑交互 static class ClientHandler implements Runnable { private Socket socket; private DataInputStream inputStream; private DataOutputStream outputStream; private int playerId; public ClientHandler(Socket socket, int playerId) { this.socket = socket; this.playerId = playerId; try { inputStream = new DataInputStream(socket.getInputStream()); outputStream = new DataOutputStream(socket.getOutputStream()); } catch (Exception e) { e.printStackTrace(); } } @Override public void run() { try { // 向客户端发送初始提示消息,必须flush outputStream.writeUTF("欢迎加入游戏!你是玩家" + (playerId + 1) + "\n请输入你的选择:石头/剪刀/布"); outputStream.flush(); // 读取客户端的选择 String playerChoice = inputStream.readUTF().trim(); // 同步更新共享的选择状态,避免并发冲突 synchronized (Server_Game.class) { playerChoices[playerId] = playerChoice; choiceCount++; } // 等待另一个玩家完成选择 while (choiceCount < 2) { Thread.sleep(100); // 短暂休眠,减少CPU占用 } // 计算游戏结果 String result = calculateResult(playerChoice, playerChoices[1 - playerId]); // 向当前玩家发送结果 sendMessage("你的选择:" + playerChoice + "\n对方选择:" + playerChoices[1 - playerId] + "\n结果:" + result); // 向另一个玩家发送结果 ClientHandler opponentHandler = clientHandlers.get(1 - playerId); opponentHandler.sendMessage("你的选择:" + playerChoices[1 - playerId] + "\n对方选择:" + playerChoice + "\n结果:" + result); // 重置游戏状态,准备下一轮 synchronized (Server_Game.class) { choiceCount = 0; playerChoices = new String[2]; } } catch (Exception e) { System.out.println("玩家" + (playerId + 1) + "连接异常:" + e.getMessage()); e.printStackTrace(); } finally { // 关闭资源并移除客户端处理器 try { if (inputStream != null) inputStream.close(); if (outputStream != null) outputStream.close(); if (socket != null) socket.close(); clientHandlers.remove(this); } catch (Exception e) { e.printStackTrace(); } } } // 封装消息发送逻辑,确保flush private void sendMessage(String message) throws Exception { outputStream.writeUTF(message); outputStream.flush(); } // 计算石头剪刀布的结果 private String calculateResult(String myChoice, String opponentChoice) { if (myChoice.equalsIgnoreCase(opponentChoice)) { return "平局"; } switch (myChoice.toLowerCase()) { case "石头": return opponentChoice.equalsIgnoreCase("剪刀") ? "你赢了!" : "你输了..."; case "剪刀": return opponentChoice.equalsIgnoreCase("布") ? "你赢了!" : "你输了..."; case "布": return opponentChoice.equalsIgnoreCase("石头") ? "你赢了!" : "你输了..."; default: return "无效选择,游戏结果无效"; } } } }
关键修改点说明
- 独立线程处理客户端:每个客户端连接后都会启动一个
ClientHandler线程,避免主线程被单个客户端的IO操作阻塞,确保两个客户端的消息都能被及时处理。 - 线程安全的客户端管理:使用
CopyOnWriteArrayList存储客户端处理器,避免多线程环境下的并发修改异常。 - 强制flush输出流:封装了
sendMessage方法,每次发送消息后都会调用flush(),确保缓冲区的消息立即发送到客户端。 - 同步共享状态:用
synchronized块保护playerChoices和choiceCount这两个共享变量,避免并发读写导致的逻辑错误。
另外,记得检查你的客户端代码,确保客户端在读取服务器消息时没有出现阻塞(比如客户端是否在readUTF()后正确处理了输入,没有死循环等问题)。
内容的提问来源于stack exchange,提问作者sucket
相关产品推荐
相关产品推荐

