基于Node、Express与Socket.io的类棋类游戏匹配后统一重定向方案咨询
实现棋类游戏匹配与重定向的可行方案
我来帮你梳理一套能解决当前问题的实现思路,核心是把匹配请求排队和游戏阶段Socket连接拆分开,避免重定向导致的Socket断开问题,同时还原类似chess.com的匹配逻辑。
核心思路
点击PLAY按钮后,先让服务器把玩家加入匹配队列,不立即重定向;当队列凑齐2人时,服务器生成唯一GameID,主动通知两位玩家跳转至游戏页面;玩家进入游戏页面后,再建立新的Socket连接并加入对应游戏房间,启动游戏。
具体代码实现
1. 首页客户端代码(处理匹配请求)
点击PLAY时先建立匹配专用的Socket,发送匹配请求后等待服务器的匹配成功通知:
// 首页的JS代码 let matchSocket; function play() { // 建立匹配阶段的Socket连接 matchSocket = io(); // 监听匹配成功的通知,收到后跳转游戏页面 matchSocket.on("match_success", (gameInfo) => { window.location.href = `/game/${gameInfo.gameId}`; matchSocket.disconnect(); // 匹配完成,断开当前Socket }); // 监听匹配超时/失败的情况(可选) matchSocket.on("match_timeout", () => { alert("匹配超时,请重试"); matchSocket.disconnect(); }); // 发送加入匹配队列的请求 matchSocket.emit("join_match_queue"); }
2. 服务器端核心逻辑(匹配队列+房间管理)
维护等待队列和游戏房间映射,处理匹配请求和游戏房间连接:
const express = require('express'); const http = require('http'); const { Server } = require('socket.io'); const app = express(); const server = http.createServer(app); const io = new Server(server); // 等待匹配的玩家队列(存储Socket实例) const waitingPlayers = []; // 游戏房间映射:key=GameID,value=房间信息 const gameRooms = new Map(); io.on('connection', (socket) => { // 处理玩家加入匹配队列 socket.on('join_match_queue', () => { console.log(`玩家 ${socket.id} 加入匹配队列`); waitingPlayers.push(socket); // 检查队列是否凑齐2人 if (waitingPlayers.length >= 2) { // 取出前两位玩家 const player1 = waitingPlayers.shift(); const player2 = waitingPlayers.shift(); // 生成唯一GameID(时间戳+随机字符串保证唯一性) const gameId = `game_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`; // 初始化游戏房间信息 gameRooms.set(gameId, { players: [player1.id, player2.id], status: 'ready' }); // 通知两位玩家匹配成功,发送GameID player1.emit('match_success', { gameId }); player2.emit('match_success', { gameId }); console.log(`匹配成功:游戏ID ${gameId},玩家 ${player1.id} & ${player2.id}`); } }); // 处理游戏页面的Socket连接请求 socket.on('join_game_room', (gameId) => { const room = gameRooms.get(gameId); if (!room) { socket.emit('error', '无效的游戏房间'); window.location.href = '/'; return; } // 让Socket加入对应游戏房间 socket.join(gameId); room.status = 'playing'; // 通知房间内所有玩家游戏开始 io.to(gameId).emit('game_start', { gameId, players: room.players, firstPlayer: room.players[0] }); console.log(`玩家 ${socket.id} 加入游戏房间 ${gameId}`); }); // 处理玩家断开连接的异常情况 socket.on('disconnect', () => { // 从匹配队列中移除断开的玩家 const queueIndex = waitingPlayers.indexOf(socket); if (queueIndex !== -1) { waitingPlayers.splice(queueIndex, 1); console.log(`玩家 ${socket.id} 退出匹配队列`); } // 处理游戏中玩家断开的情况 for (const [gameId, room] of gameRooms.entries()) { const playerIndex = room.players.indexOf(socket.id); if (playerIndex !== -1) { room.players.splice(playerIndex, 1); // 通知剩余玩家对手断开 io.to(gameId).emit('opponent_disconnected'); // 房间空了就删除 if (room.players.length === 0) { gameRooms.delete(gameId); } break; } } }); }); // 托管静态页面(首页、游戏页面) app.use(express.static('public')); // 游戏页面路由 app.get('/game/:gameId', (req, res) => { res.sendFile(__dirname + '/public/game.html'); }); server.listen(3000, () => { console.log('服务器运行在 http://localhost:3000'); });
3. 游戏页面客户端代码(建立游戏Socket)
页面加载后获取URL中的GameID,连接Socket并加入游戏房间:
// 游戏页面的JS代码 const gameId = window.location.pathname.split('/')[2]; // 从URL路径提取GameID const gameSocket = io(); gameSocket.on('connect', () => { // 发送加入游戏房间的请求 gameSocket.emit('join_game_room', gameId); }); // 监听游戏开始通知,初始化游戏逻辑 gameSocket.on('game_start', (data) => { console.log('游戏开始!', data); // 这里写棋盘渲染、回合逻辑等游戏代码 }); // 监听对手断开的通知 gameSocket.on('opponent_disconnected', () => { alert('对手已断开连接,游戏结束'); window.location.href = '/'; }); // 监听错误提示 gameSocket.on('error', (msg) => { alert(msg); window.location.href = '/'; });
关键细节说明
- 分离匹配与游戏Socket:匹配阶段用单独的Socket,匹配成功后跳转,游戏阶段重新建立Socket连接,避免重定向导致的连接中断。
- 队列与房间管理:用数组维护等待队列,凑齐2人就生成房间;用Map存储游戏房间信息,方便后续游戏逻辑调用。
- 异常处理:处理玩家在匹配中、游戏中断开的情况,保证队列和房间数据的准确性,同时给用户友好提示。
内容的提问来源于stack exchange,提问作者aminef
相关产品推荐
相关产品推荐

