如何用Socket.IO、Node.js和Express服务器动态创建游戏房间?
整体实现逻辑拆解
整个方案完全覆盖你的需求,没有冗余的学习成本,所有逻辑都是可直接运行的:
- 专属游戏URL规则:统一用路径
/game/<8位随机房间ID>区分不同游戏,不需要复杂的后端路由设计 - 房间隔离机制:用Socket.IO原生的房间频道能力,每个随机ID对应一个独立的通信通道,不同频道的消息完全隔离,不会出现不同游戏的玩家收到对方消息的情况
- 用户名流程:自建简单的用户名缓存逻辑,房主在创建游戏时输入的用户名存在浏览器本地,跳转游戏页时自动带入;外部访客通过分享链接进入时,因为本地没有缓存用户名,自动弹出输入框要求自定义用户名后加入
- 统一页面逻辑:所有带游戏路径的请求,后端都返回同一个游戏页面文件,保证不管哪个游戏的页面布局、核心功能100%一致,仅通过路径里的房间ID区分通信频道
- 一键复制功能:用浏览器原生剪贴板能力,直接把当前页面的完整URL写入剪贴板,不需要额外后端接口
完整可运行代码
后端服务(server.js)
const express = require('express'); const http = require('http'); const { Server } = require('socket.io'); const path = require('path'); const app = express(); const server = http.createServer(app); const io = new Server(server); // 托管前端静态文件 app.use(express.static(path.join(__dirname, 'public'))); // 统一游戏页路由:不管房间ID是啥,都返回同一个游戏页面 app.get('/game/:roomId', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'game.html')); }); // 存储所有房间的在线用户,结构:{ 房间ID: [{socketId, username}] } const roomOnlineUsers = {}; io.on('connection', (socket) => { // 处理用户加入房间的请求 socket.on('join_room', ({ roomId, username }) => { // 把当前socket绑定到对应房间频道 socket.join(roomId); // 初始化房间用户列表 if (!roomOnlineUsers[roomId]) roomOnlineUsers[roomId] = []; // 写入当前用户信息 roomOnlineUsers[roomId].push({ id: socket.id, username: username }); // 给房间内所有在线用户广播最新的在线列表 io.to(roomId).emit('online_user_list', roomOnlineUsers[roomId]); }); // 处理用户断开连接的清理逻辑 socket.on('disconnect', () => { // 遍历所有房间,移除离线用户 for (const roomId in roomOnlineUsers) { const userIndex = roomOnlineUsers[roomId].findIndex(item => item.id === socket.id); if (userIndex > -1) { roomOnlineUsers[roomId].splice(userIndex, 1); // 给剩下的在线用户广播更新后的列表 io.to(roomId).emit('online_user_list', roomOnlineUsers[roomId]); // 空房间直接删除,释放内存 if (roomOnlineUsers[roomId].length === 0) delete roomOnlineUsers[roomId]; } } }); // 通用游戏消息转发:不绑定具体游戏逻辑,后续所有交互都可以复用这个通道 socket.on('game_interaction', ({ roomId, payload }) => { io.to(roomId).emit('game_interaction_broadcast', payload); }); }); server.listen(3000, () => { console.log('服务启动完成,访问 http://localhost:3000 即可'); });
前端页面
首页(public/index.html,创建游戏入口)
<h1>多人游戏创建页</h1> <div style="margin:20px 0;"> <input id="hostNameInput" placeholder="输入你的自定义用户名"> <button id="createRoomBtn" style="margin-left:10px;">创建新游戏</button> </div> <script> document.getElementById('createRoomBtn').addEventListener('click', () => { const username = document.getElementById('hostNameInput').value.trim(); if (!username) { alert('请先输入用户名'); return; } // 生成8位随机房间ID,不需要后端生成,降低复杂度 const roomId = Math.random().toString(36).slice(2, 10); // 把用户名存在浏览器本地,跳转后自动带入 localStorage.setItem('game_current_username', username); // 跳转到专属游戏链接 window.location.href = `/game/${roomId}`; }); </script>
统一游戏页(public/game.html,所有游戏共用同一个页面文件)
<h1>游戏房间</h1> <div style="margin:20px 0;"> <p>当前在线玩家:<span id="playerList"></span></p> <button id="copyInviteLinkBtn">点击复制专属邀请链接</button> </div> <!-- 用户名输入弹窗,默认隐藏 --> <div id="nameModal" style="display:none; position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); padding:24px; border:1px solid #eee; border-radius:8px;"> <input id="joinNameInput" placeholder="输入自定义用户名加入游戏"> <button id="confirmJoinBtn" style="margin-left:8px;">确认加入</button> </div> <script src="/socket.io/socket.io.js"></script> <script> // 从当前路径提取房间ID const currentRoomId = window.location.pathname.split('/')[2]; // 读取本地缓存的用户名 let currentUsername = localStorage.getItem('game_current_username'); // 建立socket连接 const socket = io(); // 如果本地没有缓存用户名,弹出输入框 if (!currentUsername) { document.getElementById('nameModal').style.display = 'block'; } else { // 有缓存直接加入房间 socket.emit('join_room', { roomId: currentRoomId, username: currentUsername }); } // 确认加入按钮的逻辑 document.getElementById('confirmJoinBtn').addEventListener('click', () => { currentUsername = document.getElementById('joinNameInput').value.trim(); if (!currentUsername) { alert('请输入用户名'); return; } localStorage.setItem('game_current_username', currentUsername); document.getElementById('nameModal').style.display = 'none'; socket.emit('join_room', { roomId: currentRoomId, username: currentUsername }); }); // 一键复制链接逻辑 document.getElementById('copyInviteLinkBtn').addEventListener('click', async () => { await navigator.clipboard.writeText(window.location.href); alert('邀请链接已复制'); }); // 接收服务端推送的在线玩家列表,更新页面显示 socket.on('online_user_list', (userList) => { document.getElementById('playerList').textContent = userList.map(item => item.username).join('、'); }); // 后续游戏交互逻辑的接收通道 socket.on('game_interaction_broadcast', (payload) => { // 在这里写收到其他玩家操作后的更新逻辑,比如移动角色、提交选择等 console.log('收到同房间玩家操作:', payload); }); </script>
代码扩展说明
- 后续要加任何游戏内的交互,只需要在前端触发操作时,发送带当前房间ID的
game_interaction事件,服务端会自动把消息转发给同房间的所有玩家,不需要修改基础的房间逻辑 - 所有房间的通信是完全隔离的,哪怕同时开100个游戏房间,互相之间也不会有消息串扰
- 因为所有游戏共用同一个页面文件,后续修改布局、功能的时候,所有房间会同步生效,不需要单独维护不同游戏的页面
运行步骤
- 新建项目文件夹,执行
npm init -y初始化项目 - 安装依赖:
npm install express socket.io - 按照上面的目录结构创建
server.js,在项目下新建public文件夹,里面放index.html和game.html - 执行
node server.js启动服务,访问http://localhost:3000即可测试全流程
内容的提问来源于stack exchange,提问作者Larry Schwall
相关产品推荐
相关产品推荐

