You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在多页面维持同一Socket连接?卡牌游戏开发遇阻

多玩家卡牌游戏Socket连接复用问题解决方案

问题描述

开发多玩家房间制卡牌游戏时,遇到Socket连接复用问题:

  • 大厅页面调用io()创建Socket连接,用于触发创建/加入房间事件
  • 跳转至房间页后,导入连接时会重新执行io()生成新连接
  • 新连接未加入原房间,无法接收server:newConnection事件,导致老玩家看不到新加入的玩家

解决方案

方案1:传递房间编码,在房间页重新加入对应房间

页面跳转后JS会重新加载,原Socket连接会断开,因此需要在房间页拿到房间编码后,主动让新连接加入房间,同时服务端补充逻辑同步房间内玩家列表。

修改客户端大厅代码

跳转房间页时,把房间编码存入sessionStorage:

const socket = io();

$("#createParty").click(() => {
  const code = newCode();
  // 存储房间编码到sessionStorage
  sessionStorage.setItem("roomCode", code);
  socket.emit("client: newParty", {userName: sessionStorage.getItem("userName"), code: code});
  window.location.href = "/room";
});

$("#connectParty").click(() => {
  const codeConnect = $("#txt-codeConnect").val();
  // 存储房间编码到sessionStorage
  sessionStorage.setItem("roomCode", codeConnect);
  socket.emit("client: tryConnectParty", {userName: sessionStorage.getItem("userName"), codeConnect: codeConnect});
  window.location.href = "/room";
});

export default socket;

修改客户端房间页代码

导入Socket后,主动发送加入房间的请求,并请求当前房间玩家列表:

import socket from "/main.js";

const userName = sessionStorage.getItem("userName");
const roomCode = sessionStorage.getItem("roomCode");
$("#txt-userName").html(`<b class='text-white'>${userName}</b>`);
$("#txt-codeRoom").html(`<b>${roomCode}</b>`);

var divPlayers = $("#players");

// 主动加入房间
socket.emit("client: joinRoom", { userName, roomCode });

// 接收玩家加入事件
socket.on("server: newConnection", (userName) => {
  divPlayers.append(createCard(userName));
});

// 接收房间现有玩家列表
socket.on("server: roomPlayers", (players) => {
  // 清空现有内容,添加所有玩家
  divPlayers.empty();
  players.forEach(player => {
    divPlayers.append(createCard(player));
  });
});

function createCard(user) {
  return `
      <div class="card d-inline-flex text-center" style="width: 300px;">
          <div class="card-body">
          <span class="h1 text-dark">${user}</span>
          </div>
          <img class="card-img-bottom" src="img/avatar.png" alt="avatar">
      </div>
    `;
}

修改服务端代码

新增client: joinRoom事件处理,以及同步玩家列表的逻辑:

// Import for server...
import { Server as WebSocketServer } from "socket.io";
import http from "http";
import app from "./app.js";

const server = http.createServer(app);
const io = new WebSocketServer(server);

var rooms = [];
export var usersRoom = [];

// Events socket...
io.on("connection", (socket) => { 
  console.log("New conection:", socket.id);

  socket.on("client: newParty", (data) => { // data = {userName, code} 
    const code = data.code; 
    const roomId = "room-"+ code;

    socket.join(roomId);  
    rooms.push({ 
      room: code, 
      users: [data.userName]
    }); 
  });

  socket.on("client: tryConnectParty", (data) => { // data = {userName, codeConnect} 
    const code = data.codeConnect;
    const roomId = "room-"+ code;
    for (let i = 0; i < rooms.length; i++) {
      if (rooms[i].room == code) {
        const userName = data.userName;
        socket.join(roomId);
        rooms[i].users.push(userName);
        usersRoom = rooms[i].users;
        // 发送给房间内其他玩家
        socket.to(roomId).emit("server: newConnection", userName);
        // 通知当前玩家房间内的所有玩家
        socket.emit("server: roomPlayers", rooms[i].users);
        break;
      }
    }
  });

  // 新增:处理房间页主动加入房间的请求
  socket.on("client: joinRoom", (data) => {
    const { roomCode, userName } = data;
    const roomId = "room-" + roomCode;
    // 找到对应的房间
    const room = rooms.find(r => r.room === roomCode);
    if (room) {
      socket.join(roomId);
      // 发送房间现有玩家列表给当前用户
      socket.emit("server: roomPlayers", room.users);
    }
  });
});

export default server;

方案2:使用Socket.IO的查询参数传递房间信息

在初始化Socket时,通过查询参数携带房间编码,服务端自动让连接加入对应房间。

修改客户端大厅代码

跳转时存储房间编码,房间页初始化Socket时携带参数:

// 大厅代码不变,依然存储roomCode到sessionStorage
const socket = io();

$("#createParty").click(() => {
  const code = newCode();
  sessionStorage.setItem("roomCode", code);
  socket.emit("client: newParty", {userName: sessionStorage.getItem("userName"), code: code});
  window.location.href = "/room";
});

$("#connectParty").click(() => {
  const codeConnect = $("#txt-codeConnect").val();
  sessionStorage.setItem("roomCode", codeConnect);
  socket.emit("client: tryConnectParty", {userName: sessionStorage.getItem("userName"), codeConnect: codeConnect});
  window.location.href = "/room";
});

export default socket;

修改客户端房间页代码

初始化Socket时携带房间编码参数:

// 不再导入大厅的socket,而是直接创建带参数的连接
const roomCode = sessionStorage.getItem("roomCode");
const socket = io({
  query: { roomCode, userName: sessionStorage.getItem("userName") }
});

const userName = sessionStorage.getItem("userName");
$("#txt-userName").html(`<b class='text-white'>${userName}</b>`);
$("#txt-codeRoom").html(`<b>${roomCode}</b>`);

var divPlayers = $("#players");

socket.on("server: newConnection", (userName) => {
  divPlayers.append(createCard(userName));
});

socket.on("server: roomPlayers", (players) => {
  divPlayers.empty();
  players.forEach(player => {
    divPlayers.append(createCard(player));
  });
});

function createCard(user) {
  return `
      <div class="card d-inline-flex text-center" style="width: 300px;">
          <div class="card-body">
          <span class="h1 text-dark">${user}</span>
          </div>
          <img class="card-img-bottom" src="img/avatar.png" alt="avatar">
      </div>
    `;
}

修改服务端代码

在connection事件中自动处理查询参数,让连接加入房间:

// Import for server...
import { Server as WebSocketServer } from "socket.io";
import http from "http";
import app from "./app.js";

const server = http.createServer(app);
const io = new WebSocketServer(server);

var rooms = [];
export var usersRoom = [];

// Events socket...
io.on("connection", (socket) => { 
  console.log("New conection:", socket.id);
  const { roomCode, userName } = socket.handshake.query;
  
  // 如果携带了房间编码,自动加入房间
  if (roomCode && userName) {
    const roomId = "room-" + roomCode;
    const room = rooms.find(r => r.room === roomCode);
    if (room) {
      socket.join(roomId);
      socket.emit("server: roomPlayers", room.users);
    }
  }

  socket.on("client: newParty", (data) => { // data = {userName, code} 
    const code = data.code; 
    const roomId = "room-"+ code;

    socket.join(roomId);  
    rooms.push({ 
      room: code, 
      users: [data.userName]
    }); 
  });

  socket.on("client: tryConnectParty", (data) => { // data = {userName, codeConnect} 
    const code = data.codeConnect;
    const roomId = "room-"+ code;
    for (let i = 0; i < rooms.length; i++) {
      if (rooms[i].room == code) {
        const userName = data.userName;
        socket.join(roomId);
        rooms[i].users.push(userName);
        usersRoom = rooms[i].users;
        socket.to(roomId).emit("server: newConnection", userName);
        socket.emit("server: roomPlayers", rooms[i].users);
        break;
      }
    }
  });
});

export default server;

关键说明

页面跳转后浏览器会重新加载所有JS资源,原Socket连接会被断开,因此无法直接复用之前的连接。核心思路是在房间页重新建立连接后,主动让连接加入对应的房间,并同步房间内的玩家状态。

内容的提问来源于stack exchange,提问作者Faihd Enrique Pineda Duque

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 02:20:28