
暑假是提升技能、沉淀项目经验的黄金时期尤其对于有明确职业规划的开发者而言一个能串联起前后端、数据库、网络通信等核心技术的实战项目其价值远超零散的知识点学习。本文将围绕一个经典的“网络对战五子棋游戏”项目从零开始手把手带你完成一个功能完整、架构清晰、代码可复用的实战案例。无论你是想夯实Java基础、学习Socket编程、理解多线程并发还是为简历增添一个亮眼的项目这篇教程都能为你提供一条清晰的路径。1. 项目背景与核心价值1.1 为什么选择五子棋游戏作为实战项目五子棋规则简单但其背后的技术实现却涵盖了软件开发中的多个核心模块网络通信实现玩家间的实时对战是理解Socket编程、TCP/IP协议的绝佳场景。多线程并发服务器需要同时处理多个客户端的连接和请求这是学习线程池、同步机制如synchronized的经典案例。图形用户界面GUI使用Swing或JavaFX绘制棋盘、棋子处理鼠标事件是桌面应用开发的基础。业务逻辑与状态管理游戏规则输赢判定、棋盘状态、玩家回合等逻辑的实现锻炼你的业务抽象和编码能力。客户端-服务器C/S架构这是现代分布式系统的基础模型通过本项目你能深刻理解客户端与服务器的职责划分和数据交互流程。完成这个项目你不仅能得到一个可运行的游戏更能建立起对“一个完整应用程序如何运作”的系统性认知。1.2 项目最终效果与技术栈我们将构建一个支持两人在线对战的五子棋游戏。服务器端负责管理游戏房间、匹配玩家、转发落子信息、判断游戏胜负。客户端提供图形化界面接收玩家操作并与服务器通信。核心技术栈Java SE作为主要开发语言。Java Socket用于网络通信。多线程处理并发连接。Swing用于构建客户端图形界面也可选用JavaFX本文以Swing为例。JSON或简单自定义协议用于客户端与服务器之间的数据交换格式。2. 环境准备与项目结构2.1 开发环境要求操作系统Windows / macOS / Linux 均可。JDK版本JDK 8 或以上版本推荐JDK 11或17确保稳定性。可通过java -version命令验证。IDEIntelliJ IDEA推荐、Eclipse 或 VS Code。本文示例使用 IntelliJ IDEA。构建工具本项目使用Maven管理依赖但核心依赖仅为JDK内置库也可直接创建普通Java项目。2.2 项目结构规划在开始编码前清晰的目录结构有助于管理代码。我们创建两个独立的工程或模块gomoku-server服务器和gomoku-client客户端。服务器端 (gomoku-server) 结构gomoku-server/ ├── pom.xml (Maven配置文件) └── src/ └── main/ └── java/ └── com/ └── yourname/ └── gomoku/ ├── server/ │ ├── GomokuServer.java (服务器主启动类) │ ├── handler/ │ │ ├── ClientHandler.java (处理单个客户端连接的线程类) │ │ └── GameRoom.java (游戏房间管理一场对局) │ ├── model/ │ │ ├── Message.java (消息实体类) │ │ ├── Player.java (玩家实体类) │ │ └── ChessPoint.java (棋子坐标) │ └── util/ │ └── JsonUtil.java (JSON工具类可选)客户端端 (gomoku-client) 结构gomoku-client/ ├── pom.xml └── src/ └── main/ └── java/ └── com/ └── yourname/ └── gomoku/ ├── client/ │ ├── GomokuClient.java (客户端主类含界面和网络连接) │ ├── ui/ │ │ ├── ChessBoardPanel.java (棋盘绘制面板) │ │ └── LoginFrame.java (登录/连接窗口可选) │ ├── model/ │ │ └── (可复用服务器的Message, ChessPoint等) │ └── net/ │ └── ServerConnector.java (封装网络通信的类)3. 核心设计与协议定义3.1 通信协议设计客户端与服务器之间需要交换数据。我们定义一个简单的基于文本的协议使用JSON格式便于扩展。消息类型定义如下// 文件gomoku-common/src/main/java/com/yourname/gomoku/model/Message.java // 这是一个通用的消息类服务器和客户端可共用或分别定义 public class Message { private String type; // 消息类型 private String sender; // 发送者 private String content; // 消息内容 private Object data; // 附加数据如棋子坐标 // 构造函数、Getter和Setter省略... // 定义消息类型常量 public static final String TYPE_LOGIN LOGIN; public static final String TYPE_JOIN_ROOM JOIN_ROOM; public static final String TYPE_READY READY; public static final String TYPE_CHESS_PUT CHESS_PUT; public static final String TYPE_GAME_OVER GAME_OVER; public static final String TYPE_CHAT CHAT; public static final String TYPE_ERROR ERROR; }关键消息流连接与登录客户端发送{type:LOGIN, sender:玩家A}。匹配与准备服务器分配房间通知双方玩家{type:JOIN_ROOM, data:{roomId:1, opponent:玩家B, myColor:BLACK}}。玩家准备后发送{type:READY}。落子玩家落子后客户端发送{type:CHESS_PUT, data:{x:10, y:10}}给服务器服务器校验后转发给对手。游戏结束当一方获胜或和棋服务器广播{type:GAME_OVER, data:{winner:玩家A, reason:FIVE_IN_A_ROW}}。3.2 核心类职责GomokuServer启动ServerSocket监听端口接受客户端连接使用线程池管理ClientHandler。ClientHandler一个线程对应一个客户端连接。负责读取该客户端发送的消息解析后交给GameRoom处理并将处理结果写回客户端。GameRoom管理一场对局的生命周期。持有两个Player对象维护当前棋盘状态二维数组负责落子合法性校验、胜负判定并向房间内两个客户端广播游戏状态。GomokuClient包含Swing主窗口初始化界面组件并启动一个线程或使用ServerConnector与服务器保持通信。ChessBoardPanel继承JPanel负责绘制棋盘网格和棋子并监听鼠标点击事件将物理坐标转换为棋盘逻辑坐标。4. 服务器端核心实现4.1 服务器主类与线程池// 文件gomoku-server/src/main/java/com/yourname/gomoku/server/GomokuServer.java public class GomokuServer { private static final int PORT 8888; private ServerSocket serverSocket; private ExecutorService threadPool; // 线程池处理客户端连接 private AtomicInteger roomIdGenerator new AtomicInteger(1); // 房间ID生成器 private MapInteger, GameRoom gameRooms new ConcurrentHashMap(); // 活跃房间列表 public void start() { try { serverSocket new ServerSocket(PORT); threadPool Executors.newCachedThreadPool(); // 使用缓存线程池 System.out.println(五子棋服务器启动监听端口: PORT); while (true) { Socket clientSocket serverSocket.accept(); System.out.println(客户端连接: clientSocket.getInetAddress()); // 为每个客户端连接创建一个处理线程 ClientHandler clientHandler new ClientHandler(clientSocket, this); threadPool.execute(clientHandler); } } catch (IOException e) { e.printStackTrace(); } finally { // 关闭资源... } } // 创建房间的方法供ClientHandler调用 public synchronized GameRoom createRoom(Player player1, Player player2) { int roomId roomIdGenerator.getAndIncrement(); GameRoom room new GameRoom(roomId, player1, player2); gameRooms.put(roomId, room); System.out.println(创建房间: roomId); return room; } // 根据房间ID获取房间 public GameRoom getRoom(int roomId) { return gameRooms.get(roomId); } // 移除房间 public void removeRoom(int roomId) { gameRooms.remove(roomId); System.out.println(移除房间: roomId); } public static void main(String[] args) { new GomokuServer().start(); } }4.2 客户端连接处理器// 文件gomoku-server/src/main/java/com/yourname/gomoku/server/handler/ClientHandler.java public class ClientHandler implements Runnable { private Socket socket; private GomokuServer server; private PrintWriter out; private BufferedReader in; private Player currentPlayer; private GameRoom currentRoom; public ClientHandler(Socket socket, GomokuServer server) { this.socket socket; this.server server; } Override public void run() { try { in new BufferedReader(new InputStreamReader(socket.getInputStream())); out new PrintWriter(socket.getOutputStream(), true); // autoFlush String messageStr; while ((messageStr in.readLine()) ! null) { // 按行读取消息 System.out.println(收到消息: messageStr); // 解析JSON消息为Message对象 (使用如Gson库此处简化) Message message parseMessage(messageStr); processMessage(message); } } catch (IOException e) { System.out.println(客户端连接断开: (currentPlayer ! null ? currentPlayer.getName() : 未知)); } finally { // 清理资源如玩家退出房间等 cleanup(); } } private void processMessage(Message message) { switch (message.getType()) { case Message.TYPE_LOGIN: handleLogin(message); break; case Message.TYPE_JOIN_ROOM: handleJoinRoom(message); break; case Message.TYPE_CHESS_PUT: handleChessPut(message); break; case Message.TYPE_READY: handleReady(message); break; // ... 处理其他消息类型 } } private void handleLogin(Message message) { String playerName message.getSender(); this.currentPlayer new Player(playerName, this); // Player持有当前Handler的引用用于回写 sendMessage(new Message(Message.TYPE_LOGIN, SERVER, 登录成功)); // 这里可以加入匹配逻辑找到等待的玩家后创建房间 // 简化版假设立刻匹配了一个机器人或等待另一个玩家 System.out.println(playerName 登录成功); } private void handleChessPut(Message message) { if (currentRoom null) { sendError(你不在任何房间中); return; } // 将落子请求交给房间处理房间会进行校验和广播 currentRoom.putChess(currentPlayer, (ChessPoint) message.getData()); } // 发送消息给当前客户端 public void sendMessage(Message message) { String jsonStr convertToJson(message); // 转换为JSON字符串 out.println(jsonStr); } private void sendError(String errorMsg) { sendMessage(new Message(Message.TYPE_ERROR, SERVER, errorMsg)); } // 其他方法parseMessage, convertToJson, cleanup等... }4.3 游戏房间与胜负判定// 文件gomoku-server/src/main/java/com/yourname/gomoku/server/handler/GameRoom.java public class GameRoom { private int roomId; private Player blackPlayer; // 黑方 private Player whitePlayer; // 白方 private Player currentPlayer; // 当前行棋方 private int[][] board; // 15x15棋盘0空1黑2白 private boolean gameStarted false; public GameRoom(int roomId, Player player1, Player player2) { this.roomId roomId; // 简单分配黑白 this.blackPlayer player1; this.whitePlayer player2; this.blackPlayer.setColor(Player.Color.BLACK); this.whitePlayer.setColor(Player.Color.WHITE); this.currentPlayer blackPlayer; // 黑先 this.board new int[15][15]; // 通知双方玩家加入房间及颜色 notifyPlayersJoined(); } public synchronized void putChess(Player player, ChessPoint point) { // 1. 校验是否轮到该玩家 if (player ! currentPlayer) { player.sendMessage(new Message(Message.TYPE_ERROR, SERVER, 还没轮到你下棋)); return; } // 2. 校验位置是否合法且在棋盘内、是否为空 if (!isValidPosition(point) || board[point.getY()][point.getX()] ! 0) { player.sendMessage(new Message(Message.TYPE_ERROR, SERVER, 非法落子位置)); return; } // 3. 落子 int chessValue player.getColor() Player.Color.BLACK ? 1 : 2; board[point.getY()][point.getX()] chessValue; // 4. 广播落子信息给双方 Message moveMsg new Message(Message.TYPE_CHESS_PUT, player.getName(), null); moveMsg.setData(point); broadcastMessage(moveMsg); // 5. 判断胜负 if (checkWin(point.getX(), point.getY(), chessValue)) { Message winMsg new Message(Message.TYPE_GAME_OVER, SERVER, 游戏结束); winMsg.setData(Map.of(winner, player.getName(), reason, FIVE_IN_A_ROW)); broadcastMessage(winMsg); gameStarted false; // 通知服务器移除房间 // server.removeRoom(roomId); return; } // 6. 切换行棋方 currentPlayer (currentPlayer blackPlayer) ? whitePlayer : blackPlayer; // 可以广播一个 TURN 消息通知当前轮到谁 } private boolean checkWin(int x, int y, int color) { // 五子棋胜负判定检查横、竖、左斜、右斜四个方向是否有连续5个同色棋子 int[][] directions {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; // 横、竖、右斜、左斜 for (int[] dir : directions) { int count 1; // 当前落子点本身 // 正向检查 for (int step 1; step 5; step) { int newX x dir[0] * step; int newY y dir[1] * step; if (isInBoard(newX, newY) board[newY][newX] color) { count; } else { break; } } // 反向检查 for (int step 1; step 5; step) { int newX x - dir[0] * step; int newY y - dir[1] * step; if (isInBoard(newX, newY) board[newY][newX] color) { count; } else { break; } } if (count 5) { return true; } } return false; } private boolean isInBoard(int x, int y) { return x 0 x 15 y 0 y 15; } private boolean isValidPosition(ChessPoint point) { return isInBoard(point.getX(), point.getY()); } private void broadcastMessage(Message message) { blackPlayer.sendMessage(message); whitePlayer.sendMessage(message); } private void notifyPlayersJoined() { // 通知黑方 MapString, Object dataForBlack new HashMap(); dataForBlack.put(roomId, roomId); dataForBlack.put(opponent, whitePlayer.getName()); dataForBlack.put(myColor, BLACK); blackPlayer.sendMessage(new Message(Message.TYPE_JOIN_ROOM, SERVER, dataForBlack)); // 通知白方 MapString, Object dataForWhite new HashMap(); dataForWhite.put(roomId, roomId); dataForWhite.put(opponent, blackPlayer.getName()); dataForWhite.put(myColor, WHITE); whitePlayer.sendMessage(new Message(Message.TYPE_JOIN_ROOM, SERVER, dataForWhite)); } }5. 客户端核心实现5.1 客户端主界面与网络连接// 文件gomoku-client/src/main/java/com/yourname/gomoku/client/GomokuClient.java public class GomokuClient extends JFrame { private ChessBoardPanel chessBoardPanel; private JLabel statusLabel; private Socket socket; private PrintWriter out; private BufferedReader in; private String playerName; private String myColor; public GomokuClient(String serverAddress, int port, String playerName) throws IOException { this.playerName playerName; setTitle(五子棋对战 - playerName); setSize(600, 700); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new BorderLayout()); // 初始化棋盘面板 chessBoardPanel new ChessBoardPanel(this); add(chessBoardPanel, BorderLayout.CENTER); // 状态栏 statusLabel new JLabel(正在连接服务器...); add(statusLabel, BorderLayout.SOUTH); // 连接服务器 connectToServer(serverAddress, port); // 启动线程监听服务器消息 new Thread(new ServerMessageListener()).start(); setVisible(true); } private void connectToServer(String address, int port) throws IOException { socket new Socket(address, port); out new PrintWriter(socket.getOutputStream(), true); in new BufferedReader(new InputStreamReader(socket.getInputStream())); // 发送登录消息 Message loginMsg new Message(Message.TYPE_LOGIN, playerName, 请求登录); sendMessage(loginMsg); statusLabel.setText(已连接等待匹配...); } public void sendMessage(Message message) { String jsonStr convertToJson(message); out.println(jsonStr); } // 内部类用于监听服务器消息 private class ServerMessageListener implements Runnable { Override public void run() { try { String messageStr; while ((messageStr in.readLine()) ! null) { Message message parseMessage(messageStr); SwingUtilities.invokeLater(() - handleServerMessage(message)); } } catch (IOException e) { SwingUtilities.invokeLater(() - statusLabel.setText(与服务器连接断开)); } } } private void handleServerMessage(Message message) { switch (message.getType()) { case Message.TYPE_JOIN_ROOM: MapString, Object data (Map) message.getData(); String opponent (String) data.get(opponent); myColor (String) data.get(myColor); statusLabel.setText(匹配成功对手: opponent 你的棋子颜色: myColor); chessBoardPanel.setGameStarted(true); chessBoardPanel.setCurrentColor(myColor.equals(BLACK) ? ChessBoardPanel.BLACK : ChessBoardPanel.WHITE); break; case Message.TYPE_CHESS_PUT: ChessPoint point (ChessPoint) message.getData(); // 对手落子更新棋盘 chessBoardPanel.placeChess(point.getX(), point.getY(), message.getSender().equals(playerName) ? myColor : getOppositeColor(myColor)); // 切换行棋指示 chessBoardPanel.switchTurn(); break; case Message.TYPE_GAME_OVER: MapString, Object result (Map) message.getData(); String winner (String) result.get(winner); if (winner.equals(playerName)) { statusLabel.setText(恭喜你获胜了); } else { statusLabel.setText(游戏结束 winner 获胜。); } chessBoardPanel.setGameStarted(false); break; case Message.TYPE_ERROR: JOptionPane.showMessageDialog(this, 错误: message.getContent(), 错误, JOptionPane.ERROR_MESSAGE); break; } } private String getOppositeColor(String color) { return color.equals(BLACK) ? WHITE : BLACK; } // 主函数启动客户端 public static void main(String[] args) { // 可以弹出一个对话框让用户输入服务器地址和昵称 String serverAddress localhost; // 默认本地 int port 8888; String playerName JOptionPane.showInputDialog(请输入你的昵称:); if (playerName null || playerName.trim().isEmpty()) { playerName 玩家 System.currentTimeMillis() % 1000; } SwingUtilities.invokeLater(() - { try { new GomokuClient(serverAddress, port, playerName); } catch (IOException e) { JOptionPane.showMessageDialog(null, 无法连接到服务器: e.getMessage(), 连接错误, JOptionPane.ERROR_MESSAGE); } }); } }5.2 棋盘绘制与事件处理// 文件gomoku-client/src/main/java/com/yourname/gomoku/client/ui/ChessBoardPanel.java public class ChessBoardPanel extends JPanel implements MouseListener { public static final int BOARD_SIZE 15; // 15x15棋盘 public static final int CELL_SIZE 40; // 每个格子的像素 public static final int MARGIN 30; // 边距 public static final int BLACK 1; public static final int WHITE 2; private int[][] board new int[BOARD_SIZE][BOARD_SIZE]; // 本地棋盘状态 private boolean gameStarted false; private int currentColor BLACK; // 当前该谁下初始黑方 private GomokuClient client; private String myColorStr; // “BLACK” or “WHITE” public ChessBoardPanel(GomokuClient client) { this.client client; setPreferredSize(new Dimension(CELL_SIZE * BOARD_SIZE 2 * MARGIN, CELL_SIZE * BOARD_SIZE 2 * MARGIN)); setBackground(new Color(220, 179, 92)); // 棋盘背景色 addMouseListener(this); } Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d (Graphics2D) g; g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // 1. 绘制网格线 g2d.setColor(Color.BLACK); for (int i 0; i BOARD_SIZE; i) { // 横线 g2d.drawLine(MARGIN, MARGIN i * CELL_SIZE, MARGIN (BOARD_SIZE - 1) * CELL_SIZE, MARGIN i * CELL_SIZE); // 竖线 g2d.drawLine(MARGIN i * CELL_SIZE, MARGIN, MARGIN i * CELL_SIZE, MARGIN (BOARD_SIZE - 1) * CELL_SIZE); } // 2. 绘制五个小黑点天元和星 int[] dots {3, 7, 11}; // 第4, 8, 12条线0-based索引是3,7,11 g2d.fillOval(MARGIN dots[1] * CELL_SIZE - 4, MARGIN dots[1] * CELL_SIZE - 4, 8, 8); g2d.fillOval(MARGIN dots[0] * CELL_SIZE - 4, MARGIN dots[0] * CELL_SIZE - 4, 8, 8); g2d.fillOval(MARGIN dots[2] * CELL_SIZE - 4, MARGIN dots[0] * CELL_SIZE - 4, 8, 8); g2d.fillOval(MARGIN dots[0] * CELL_SIZE - 4, MARGIN dots[2] * CELL_SIZE - 4, 8, 8); g2d.fillOval(MARGIN dots[2] * CELL_SIZE - 4, MARGIN dots[2] * CELL_SIZE - 4, 8, 8); // 3. 绘制棋子 for (int row 0; row BOARD_SIZE; row) { for (int col 0; col BOARD_SIZE; col) { if (board[row][col] ! 0) { int x MARGIN col * CELL_SIZE; int y MARGIN row * CELL_SIZE; if (board[row][col] BLACK) { g2d.setColor(Color.BLACK); } else { g2d.setColor(Color.WHITE); } g2d.fillOval(x - 18, y - 18, 36, 36); // 棋子半径18 g2d.setColor(Color.GRAY); g2d.drawOval(x - 18, y - 18, 36, 36); } } } } Override public void mouseClicked(MouseEvent e) { if (!gameStarted) { return; } // 判断是否轮到自己下棋 int myColor myColorStr.equals(BLACK) ? BLACK : WHITE; if (currentColor ! myColor) { JOptionPane.showMessageDialog(this, 请等待对手落子, 提示, JOptionPane.INFORMATION_MESSAGE); return; } int x e.getX(); int y e.getY(); // 将像素坐标转换为棋盘坐标 int boardX Math.round((float)(x - MARGIN) / CELL_SIZE); int boardY Math.round((float)(y - MARGIN) / CELL_SIZE); // 检查坐标是否在棋盘内 if (boardX 0 || boardX BOARD_SIZE || boardY 0 || boardY BOARD_SIZE) { return; } // 检查该位置是否已有棋子 if (board[boardY][boardX] ! 0) { return; } // 在本地棋盘放置棋子 placeChess(boardX, boardY, myColorStr); // 发送落子消息给服务器 ChessPoint point new ChessPoint(boardX, boardY); Message moveMsg new Message(Message.TYPE_CHESS_PUT, client.playerName, null); moveMsg.setData(point); client.sendMessage(moveMsg); // 切换行棋方等待服务器确认和对手落子后由handleServerMessage切换 switchTurn(); } public void placeChess(int x, int y, String color) { int colorValue color.equals(BLACK) ? BLACK : WHITE; board[y][x] colorValue; repaint(); // 重绘棋盘 } public void switchTurn() { currentColor (currentColor BLACK) ? WHITE : BLACK; } public void setGameStarted(boolean started) { this.gameStarted started; } public void setCurrentColor(int color) { this.currentColor color; } public void setMyColorStr(String colorStr) { this.myColorStr colorStr; } // MouseListener 其他方法省略... }6. 运行与测试6.1 启动步骤启动服务器运行GomokuServer类的main方法。控制台应输出“五子棋服务器启动监听端口: 8888”。启动客户端A运行GomokuClient的main方法输入昵称如“玩家A”。客户端将尝试连接localhost:8888。启动客户端B再次运行GomokuClient输入另一个昵称如“玩家B”。匹配与对战服务器日志会显示连接和房间创建。两个客户端界面会显示匹配成功的信息。黑方先手可以开始在棋盘上点击落子棋子会同步显示在对方棋盘上。6.2 测试要点连接测试确保客户端能成功连接服务器并收到登录成功和加入房间的反馈。落子同步一方落子后另一方棋盘应立即显示。回合控制确保非当前行棋方无法落子客户端本地判断服务器校验。胜负判定当一方连成五子时游戏应立即结束双方收到获胜通知。异常处理尝试在已落子处再次落子、断开网络等观察程序的反应。7. 常见问题与排查思路问题现象可能原因排查与解决思路客户端无法连接服务器1. 服务器未启动。2. 端口被占用。3. 防火墙阻止。4. 地址/端口号错误。1. 检查服务器控制台是否有启动成功日志。2. 使用netstat -ano | findstr :8888(Windows) 或lsof -i:8888(Linux/Mac) 查看端口占用。3. 暂时关闭防火墙或添加规则。4. 确认客户端连接的IP和端口与服务器监听的一致。落子后对方看不到1. 消息未成功发送。2. 服务器未正确转发。3. 客户端未处理CHESS_PUT消息。4. 棋盘坐标转换错误。1. 在ClientHandler和GomokuClient的发送/接收处打印消息日志。2. 检查服务器GameRoom.broadcastMessage逻辑。3. 检查客户端handleServerMessage中对TYPE_CHESS_PUT的处理。4. 调试鼠标点击坐标到棋盘逻辑坐标的转换算法。游戏胜负判断不准1.checkWin算法有bug。2. 棋盘数组索引越界。1. 在checkWin方法内添加详细日志打印每一步检查的坐标和颜色。2. 使用单元测试针对各种赢棋情况横、竖、斜编写测试用例。多开客户端时服务器崩溃或卡顿1. 线程池配置不当。2.GameRoom或ClientHandler中有同步死锁。3. 资源如Socket未正确关闭。1. 使用Executors.newFixedThreadPool限制最大线程数。2. 检查synchronized关键字的使用范围避免嵌套锁。3. 确保所有Socket、InputStream、OutputStream都在finally块中关闭。界面刷新问题棋子不显示1.repaint()未被调用。2. 棋盘数据board更新后未触发重绘。3. Swing线程问题非EDT线程更新UI。1. 在placeChess方法中确认调用了repaint()。2. 确保所有UI更新都在SwingUtilities.invokeLater中执行。8. 项目优化与扩展建议一个基础版本完成后你可以从以下方向深化项目这会让你的项目经验更具竞争力8.1 功能扩展房间列表与选择服务器维护一个公共房间列表客户端可以查看、创建、加入指定房间。聊天功能在游戏界面侧边栏增加聊天区域支持玩家实时文字交流。观战模式允许第三方客户端以只读方式进入房间观战。悔棋与求和增加游戏控制功能需双方同意。积分与排行榜服务器持久化玩家胜负数据客户端展示排行榜。超时判负为每一步棋设置倒计时。8.2 架构与性能优化使用NIO或Netty将传统的BIO Socket替换为NIO或Netty框架以支持更高并发。引入消息队列将匹配逻辑、广播消息等异步化提高服务器响应能力。数据库持久化使用MySQL或Redis存储玩家信息、对战记录。配置化将服务器地址、端口、棋盘大小等参数抽取到配置文件中。日志框架使用Log4j2或SLF4J替代System.out.println。8.3 代码质量提升依赖注入使用Spring Framework管理服务器端的Bean如GameRoomManager。单元测试为checkWin等核心算法编写完善的单元测试JUnit。协议升级使用更高效的二进制协议如Protobuf替代JSON文本协议。前端美化使用JavaFX或甚至开发Web前端HTML5WebSocket替换Swing实现更美观的界面。8.4 部署与运维打包为JAR使用Maven Assembly插件将服务器和客户端分别打包为可执行JAR。编写启动脚本为Windows和Linux编写启动/停止脚本。容器化使用Docker将服务器端容器化便于部署。这个五子棋项目麻雀虽小五脏俱全。通过实现它你不仅能巩固Java SE的核心知识更能亲身体验一个完整网络应用的开发全流程。从设计协议、处理并发、绘制UI到调试通信、处理异常每一步都是宝贵的实战经验。建议你在完成基础功能后选择一两个扩展方向深入实践这会让你的技术理解和项目履历都更上一层楼。