JavaScript中的WebSocket:高级实时应用

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

介绍:超越基础 WebSocket

对 WebSocket 的基础理解可以让你创建简单的聊天或通知功能。然而,要构建生产级的实时应用——如多人在线游戏、金融终端、协同编辑系统——需要掌握更高级的技术。在本文中,我们将超越"Hello, World"的范畴,深入探讨如何在 Node.js 上构建可靠、可扩展且安全的 WebSocket 应用。

我们将涵盖四个关键领域:使用 Redis 进行扩展、状态与房间管理、安全与授权,以及多处理器集群化。所有代码示例均使用 JavaScript 编写,并采用 ws 库和 Node.js。

1. 使用 Pub/Sub(Redis)进行扩展

WebSocket 服务器的一个主要限制是它无法与其他实例"通信"。如果你在负载均衡器后运行两台服务器,连接到服务器 A 的客户端将无法收到通过服务器 B 发送的消息。解决方案是使用基于 Redis 的 Pub/Sub(发布-订阅)模式。

Redis 充当中央代理:每个服务器订阅一个频道并向其发布消息。这样,任何服务器发送的消息都会立即传播到所有其他服务器。

const WebSocket = require('ws');
const redis = require('redis');

const wss = new WebSocket.Server({ port: 8080 });
const publisher = redis.createClient();
const subscriber = redis.createClient();

// 订阅 'broadcast' 频道
subscriber.subscribe('broadcast');
subscriber.on('message', (channel, message) => {
  // 向所有连接的客户端广播消息
  wss.clients.forEach(client => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(message);
    }
  });
});

wss.on('connection', (ws) => {
  ws.on('message', (data) => {
    // 将消息发布到 Redis
    publisher.publish('broadcast', data.toString());
  });
});

这种方法允许你几乎无限地水平扩展应用。当添加新服务器时,它只需创建自己的 Redis 订阅即可。

2. 房间与状态管理

在大多数实时应用中,需要将客户端分组到"房间"(如聊天室、游戏大厅)并管理其状态。基础的 wss.clients 不提供此功能。让我们实现一个简单但有效的房间系统。

我们将创建一个 rooms 对象,其中键是房间名称,值是 WebSocket 客户端的 Set。并添加加入、离开以及房间内广播的方法。

const rooms = new Map();

function joinRoom(ws, roomName) {
  if (!rooms.has(roomName)) {
    rooms.set(roomName, new Set());
  }
  rooms.get(roomName).add(ws);
  // 将房间信息保存在 ws 对象上以便快速访问
  ws.room = roomName;
}

function leaveRoom(ws) {
  if (ws.room && rooms.has(ws.room)) {
    rooms.get(ws.room).delete(ws);
    if (rooms.get(ws.room).size === 0) {
      rooms.delete(ws.room);
    }
    delete ws.room;
  }
}

function broadcastToRoom(roomName, message, exclude = null) {
  if (!rooms.has(roomName)) return;
  rooms.get(roomName).forEach(client => {
    if (client !== exclude && client.readyState === WebSocket.OPEN) {
      client.send(message);
    }
  });
}

const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
  ws.on('message', (data) => {
    const msg = JSON.parse(data);
    switch (msg.type) {
      case 'join':
        joinRoom(ws, msg.room);
        break;
      case 'message':
        broadcastToRoom(ws.room, msg.text, ws);
        break;
    }
  });
  ws.on('close', () => leaveRoom(ws));
});

对于状态管理(例如玩家位置、光标位置),可以将数据存储在 Map 中

博客

书籍推荐