You need to enable JavaScript to run this app. 如何用Socket.IO、Node.js和Express服务器动态创建游戏房间?
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.05 00:15:09