WebSocket 实时通信
在现代 Web 应用中,实时通信能力(如聊天室、在线协作、实时通知等)越来越重要。WebSocket 协议可以实现客户端与服务端的双向、低延迟通信。NestJS 对 WebSocket 提供了原生支持,开发者可以通过 Gateway(网关)机制轻松实现实时功能。
WebSocket 基本原理
- WebSocket 是一种持久化的全双工通信协议,允许服务端主动向客户端推送消息。
- 与传统 HTTP 不同,WebSocket 连接建立后无需每次请求都重新握手,适合高频、低延迟的实时场景。
- 常见应用:聊天室、在线游戏、实时通知、协同编辑等。
WebSocket 与 HTTP 的区别
| 特性 | HTTP | WebSocket |
|---|---|---|
| 连接方式 | 短连接/单向 | 长连接/双向 |
| 通信模式 | 请求-响应 | 任意时刻可推送 |
| 适用场景 | 普通 API、页面加载 | 实时消息、推送 |
| 性能 | 有额外握手和头部 | 更高效、低延迟 |
在 NestJS 中集成 WebSocket
NestJS 提供了 @nestjs/websockets 包,内置 Gateway(网关)机制,支持 Socket.IO 和原生 ws 两种驱动。
安装依赖
npm install --save @nestjs/websockets @nestjs/platform-socket.io socket.io
创建 Gateway(网关)
// chat/chat.gateway.ts
import { WebSocketGateway, WebSocketServer, SubscribeMessage, MessageBody, ConnectedSocket, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
@WebSocketGateway({ namespace: '/chat', cors: true })
export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server: Server;
afterInit(server: Server) {
console.log('WebSocket 网关已初始化');
}
handleConnection(client: Socket) {
console.log(`客户端连接: ${client.id}`);
}
handleDisconnect(client: Socket) {
console.log(`客户端断开: ${client.id}`);
}
@SubscribeMessage('sendMessage')
handleMessage(@MessageBody() data: { user: string; message: string }, @ConnectedSocket() client: Socket) {
// 广播消息到所有客户端
this.server.emit('receiveMessage', data);
}
}
@WebSocketGateway:声明一个 WebSocket 网关,可指定命名空间、端口、CORS 等参数。@WebSocketServer:注入底层 Socket.IO Server 实例。@SubscribeMessage:监听客户端事件。emit:服务端主动推送事件。
在模块中注册 Gateway
// chat/chat.module.ts
import { Module } from '@nestjs/common';
import { ChatGateway } from './chat.gateway';
@Module({
providers: [ChatGateway],
})
export class ChatModule {}
客户端连接示例
// 浏览器端 Socket.IO 客户端
const socket = io('http://localhost:3000/chat');
socket.on('connect', () => {
console.log('已连接到 WebSocket 服务');
});
socket.emit('sendMessage', { user: '小明', message: 'Hello NestJS!' });
socket.on('receiveMessage', data => {
console.log('收到消息:', data);
});
单聊、群聊与广播代码实例
WebSocket 支持多种消息推送模式,常见的有:
- 单聊(点对点):只推送给指定用户
- 群聊(房间):推送给同一房间内的所有用户
- 广播:推送给所有在线用户
1. 单聊(点对点消息)
实现思路:服务端维护用户与 socket.id 的映射,发送消息时只推送给目标用户的 socket。
// chat.gateway.ts
import { WebSocketGateway, WebSocketServer, SubscribeMessage, MessageBody, ConnectedSocket } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
@WebSocketGateway({ namespace: '/chat', cors: true })
export class ChatGateway {
@WebSocketServer()
server: Server;
// 用户ID与socket.id映射
private userMap = new Map<string, string>();
handleConnection(client: Socket) {
const userId = client.handshake.query?.userId as string;
if (userId) {
this.userMap.set(userId, client.id);
}
}
handleDisconnect(client: Socket) {
// 移除断开用户
for (const [userId, socketId] of this.userMap.entries()) {
if (socketId === client.id) {
this.userMap.delete(userId);
break;
}
}
}
@SubscribeMessage('privateMessage')
handlePrivateMessage(@MessageBody() data: { toUserId: string; message: string }, @ConnectedSocket() client: Socket) {
const targetSocketId = this.userMap.get(data.toUserId);
if (targetSocketId) {
this.server.to(targetSocketId).emit('receivePrivateMessage', {
from: client.id,
message: data.message,
});
}
}
}
2. 群聊(房间消息)
实现思路:用户加入房间,消息推送到房间内所有成员。
@SubscribeMessage('joinRoom')
handleJoinRoom(@MessageBody() data: { room: string }, @ConnectedSocket() client: Socket) {
client.join(data.room);
client.emit('joinedRoom', { room: data.room });
}
@SubscribeMessage('roomMessage')
handleRoomMessage(@MessageBody() data: { room: string; message: string }, @ConnectedSocket() client: Socket) {
this.server.to(data.room).emit('receiveRoomMessage', {
from: client.id,
message: data.message,
room: data.room,
});
}
3. 广播(全局消息)
实现思路:服务端直接 emit 给所有连接的客户端。
@SubscribeMessage('broadcast')
handleBroadcast(@MessageBody() data: { message: string }) {
this.server.emit('receiveBroadcast', {
message: data.message,
});
}
客户端调用示例
// 单聊
socket.emit('privateMessage', { toUserId: 'user2', message: '你好,user2!' });
socket.on('receivePrivateMessage', data => console.log('收到私聊:', data));
// 加入房间
socket.emit('joinRoom', { room: 'group1' });
socket.emit('roomMessage', { room: 'group1', message: '大家好!' });
socket.on('receiveRoomMessage', data => console.log('收到群聊:', data));
// 广播
socket.emit('broadcast', { message: '全体注意!' });
socket.on('receiveBroadcast', data => console.log('收到广播:', data));
实际项目中建议结合用户认证、消息持久化、离线消息等功能,提升聊天体验和安全性。
典型实战场景:聊天室/实时通知
- 聊天室:用户发送消息,服务端通过 Gateway 广播到所有在线用户。
- 实时通知:如订单状态变更、系统公告等,服务端主动推送到指定客户端。
- 在线状态:可通过连接/断开事件统计在线人数。
最佳实践与常见坑点
- 命名空间与事件名要规范,避免冲突。
- 注意权限校验,如身份认证、消息过滤等。
- 合理管理连接数,防止资源泄漏。
- 服务端异常要捕获,避免影响所有连接。
- 生产环境建议开启 CORS 和安全配置。
- 可结合 Redis 实现多节点广播,适合大规模分布式部署。
WebSocket 连接鉴权实践
在实际项目中,WebSocket 连接通常需要进行身份认证(如 JWT token 校验),以防止未授权用户建立连接或发送消息。NestJS 支持在 Gateway 的 handleConnection 钩子中实现自定义鉴权逻辑。
1. 通过 handleConnection 实现 token 鉴权
// chat/chat.gateway.ts
import { WebSocketGateway, WebSocketServer, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import * as jwt from 'jsonwebtoken';
@WebSocketGateway({ namespace: '/chat', cors: true })
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server: Server;
async handleConnection(client: Socket) {
// 假设前端通过 query 传递 token
const token = client.handshake.query?.token as string;
try {
const payload = jwt.verify(token, 'your_jwt_secret'); // 替换为你的密钥
// 可将用户信息挂载到 client 对象
(client as any).user = payload;
console.log(`用户已鉴权: ${payload.username}`);
} catch (e) {
console.log('WebSocket 鉴权失败,断开连接');
client.disconnect();
}
}
handleDisconnect(client: Socket) {
console.log(`客户端断开: ${client.id}`);
}
}
2. 客户端连接时传递 token
// 浏览器端
const socket = io('http://localhost:3000/chat', {
query: { token: '用户登录后的JWT' }
});
3. 注意事项
- token 建议用 HTTP Only Cookie 或 Secure Storage 存储,防止泄露。
- 生产环境建议用 HTTPS,防止 token 被窃取。
- 鉴权失败要及时断开连接,避免非法访问。
- 如需更复杂的权限控制,可结合 Guard、角色等机制。
详细鉴权方案可参考 Socket.IO 官方文档 和 NestJS WebSocket 安全最佳实践。
WebSocket 让你的应用具备实时能力,建议多查阅 NestJS WebSocket 文档 和 Socket.IO 官方文档 掌握更多高级用法。