TypeScript上的WebSocket:实时应用的高级开发

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

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

开始课程

TypeScript上的WebSocket:构建高级实时应用

\n\n

实时应用已成为现代网络不可或缺的一部分。从聊天和通知到在线游戏和交易平台——WebSocket技术确保了客户端与服务器之间的持续双向连接。在高级层面,开发此类系统不仅需要理解协议,还需要能够构建高可用、可扩展的架构。在本文中,我们将探讨如何使用最佳实践和模式在TypeScript上创建生产级WebSocket应用。

\n\n

1. WebSocket服务器的架构与集群

\n\n

单个WebSocket服务器无法承受真实项目的负载。关键任务是组织集群并在实例之间交换消息。我们将探讨使用ws库和Redis事件总线的架构。

\n\n

安装依赖:

\n
npm install ws redis ioredis @types/ws @types/ioredis
\n\n

创建一个基本的集群服务器:

\n\n
import { WebSocketServer, WebSocket } from 'ws';\nimport Redis from 'ioredis';\n\nconst redisPub = new Redis();\nconst redisSub = new Redis();\n\nconst wss = new WebSocketServer({ port: 8080 });\n\n// 用于服务器间通信的频道\nconst CHANNEL = 'ws:messages';\n\nredisSub.subscribe(CHANNEL, (err, count) => {\n  if (err) console.error('订阅错误:', err);\n  console.log(`已订阅 ${count} 个频道`);\n});\n\n// 从其他实例接收消息\nredisSub.on('message', (channel, message) => {\n  const parsed = JSON.parse(message);\n  wss.clients.forEach((client) => {\n    if (client.readyState === WebSocket.OPEN) {\n      client.send(parsed.data);\n    }\n  });\n});\n\nwss.on('connection', (ws) => {\n  console.log('新连接');\n\n  ws.on('message', (data) => {\n    const message = data.toString();\n    // 发布到Redis供所有实例使用\n    redisPub.publish(CHANNEL, JSON.stringify({ data: message }));\n  });\n});
\n\n

优势:

\n
    \n
  • 水平扩展——可以添加任意数量的服务器
  • \n
  • 高可用性——当一个实例宕机时,其他实例继续工作
  • \n
  • 通过Redis Pub/Sub实现统一消息总线
  • \n
\n\n

2. 认证与会话管理

\n\n

在生产级应用中,不能信任未经认证的连接。我们将在建立连接时通过JWT实现令牌验证。

\n\n

安装jsonwebtoken:

\n
npm install jsonwebtoken @types/jsonwebtoken
\n\n

创建WebSocket认证中间件:

\n\n
import { IncomingMessage } from 'http';\nimport jwt from 'jsonwebtoken';\n\nconst JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';\n\ninterface AuthenticatedSocket extends WebSocket {\n  userId?: string;\n}\n\nfunction authenticate(ws: AuthenticatedSocket, req: IncomingMessage): boolean {\n  const url = new URL(req.url || '', 'http://localhost');\n  const token = url.searchParams.get('token');\n\n  if (!token) {\n    ws.close(4001, '未提供令牌');\n    return false;\n  }\n\n  try {\n    const decoded = jwt.verify(token, JWT_SECRET) as { userId: string };\n    ws.userId = decoded.userId;\n    console.log(`用户 ${decoded.userId} 已认证`);\n    return true;\n  } catch (err) {\n    ws.close(4002, '无效令牌');\n    return false;\n  }\n}\n\n// 使用\nwss.on('connection', (ws: AuthenticatedSocket, req) => {\n  if (!authenticate(ws, req)) return;\n\n  ws.on('message', (data) => {\n    // 现在我们知道了userId\n    console.log(`来自 ${ws.userId} 的消息: ${data}`);\n  });\n});
\n\n

建议:

\n
    \n
  • 在建立连接时通过查询参数或头部传递令牌
  • \n
  • 使用短生命周期的访问令牌和刷新令牌
  • \n
  • 将会话存储在Redis中

博客

书籍推荐