6月2日 01:26

NestJS 怎么做实时通信?WebSocket Gateway 和 Socket.IO 集成

NestJS 的 WebSocket 支持基于 Socket.IO,用装饰器风格的 Gateway 替代传统的事件监听写法。和 HTTP Controller 几乎一样的开发体验,底层自动处理连接、重连、房间管理。

基本 Gateway

bash
npm install @nestjs/websockets @nestjs/platform-socket.io socket.io
typescript
// chat/chat.gateway.ts @Gateway({ cors: { origin: '*' } }) export class ChatGateway { @SubscribeMessage('send_message') handleMessage(@MessageBody() data: string, @ConnectedSocket() client: Socket) { // 广播给所有连接的客户端 this.server.emit('new_message', data); } @WebSocketServer() server: Server; }

Gateway 就是 WebSocket 版的 Controller。@SubscribeMessage 等价于 @Post@MessageBody 等价于 @Body@ConnectedSocket 可以拿到底层 Socket 实例。

房间(Rooms)

Socket.IO 的房间机制让消息只发给特定用户群:

typescript
@Gateway() export class RoomGateway { @WebSocketServer() server: Server; @SubscribeMessage('join_room') handleJoinRoom(@MessageBody() room: string, @ConnectedSocket() client: Socket) { client.join(room); this.server.to(room).emit('user_joined', client.id); } @SubscribeMessage('room_message') handleRoomMessage( @MessageBody() data: { room: string; message: string }, @ConnectedSocket() client: Socket, ) { this.server.to(data.room).emit('new_message', { from: client.id, message: data.message, }); } }

client.join(room) 加入房间,this.server.to(room).emit() 只发给该房间的成员。

认证:验证 WebSocket 连接

WebSocket 连接不能用 HTTP Guard,要在握手阶段验证 Token:

typescript
@Gateway() export class ChatGateway implements OnGatewayConnection { handleConnection(client: Socket) { const token = client.handshake.auth.token; try { const payload = this.jwtService.verify(token); client.data.user = payload; // 存到 socket 上供后续使用 } catch { client.disconnect(); // Token 无效直接断开 } } }

客户端连接时带上 Token:

javascript
const socket = io('http://localhost:3000', { auth: { token: 'your-jwt-token' } });

配合 HTTP Controller

WebSocket 和 HTTP 可以共享 Service 层:

typescript
// messages/messages.module.ts @Module({ imports: [TypeOrmModule.forFeature([Message])], providers: [MessagesService, MessagesGateway], controllers: [MessagesController], }) export class MessagesModule {}
typescript
// messages/messages.controller.ts - HTTP 接口拿历史消息 @Get('history/:roomId') getHistory(@Param('roomId') roomId: string) { return this.messagesService.getHistory(roomId); } // messages/messages.gateway.ts - WebSocket 推新消息 @SubscribeMessage('send_message') async handleSendMessage(@MessageBody() data: { roomId: string; content: string }, @ConnectedSocket() client: Socket) { const message = await this.messagesService.create({ content: data.content, roomId: data.roomId, userId: client.data.user.id, }); this.server.to(data.roomId).emit('new_message', message); }

HTTP 负责历史数据的 CRUD,WebSocket 负责实时推送。两者共享同一个 Service 和数据库。

常见问题

连接频繁断开重连:通常是 Nginx 代理超时。加 proxy_read_timeout 3600sproxy_send_timeout 3600s,否则 Nginx 60 秒没数据就断开长连接。

内存泄漏:每个 Socket 连接占用约 10-50KB 内存。1 万个连接约 500MB。确保断开的客户端被正确清理——Socket.IO 默认有心跳检测,但最好加 pingTimeoutpingInterval 调优。

集群模式:单机多进程时,Socket.IO 的房间信息不跨进程共享。需要用 Redis Adapter:

typescript
import { RedisIoAdapter } from '@nestjs/platform-socket.io'; const redisIoAdapter = new RedisIoAdapter(app); await redisIoAdapter.connectToRedis('redis://localhost:6379'); app.useWebSocketAdapter(redisIoAdapter);

Redis Adapter 让所有进程通过 Redis 共享房间和消息状态。

标签:NestJS