三层架构Express Node应用中WebSockets(Socket.io)部署位置咨询
基于TypeSocket.io的Express三层架构整合方案
你的网关抽象层思路完全正确,核心是通过解耦让应用层彻底脱离对Socket.io这类具体网络实现的依赖,只依赖抽象接口完成双向通信。下面是落地的架构设计、代码示例和模式建议:
核心架构分层逻辑
保持原有的三层结构,新增网关抽象层作为网络层与应用层的双向桥梁:
- 网络/路由控制层:负责Socket.io的初始化、连接管理、事件监听/转发,是网关的具体实现载体
- 网关抽象层:定义标准化的双向通信接口,隔离应用层与网络实现细节
- 应用/服务层:游戏引擎依赖网关抽象接口,处理客户端动作并主动推送游戏状态
- 数据存储层:负责游戏状态的持久化,与WebSocket逻辑完全解耦
TypeScript代码示例
1. 网关抽象层(定义接口)
// src/gateway/game.gateway.ts import { EventEmitter } from 'events'; // 客户端动作类型定义 export type PlayerAction = { playerId: string; actionType: 'move' | 'attack' | 'joinRoom'; payload: Record<string, any>; }; // 应用层只依赖这个抽象接口 export interface IGameGateway { // 订阅客户端动作事件 onPlayerAction(callback: (action: PlayerAction) => void): void; // 推送游戏状态给单个玩家 pushToPlayer(playerId: string, event: string, data: any): void; // 广播游戏状态给房间内所有玩家 broadcastToRoom(roomId: string, event: string, data: any): void; } // Socket.io实现的网关具体类(放在网络层) export class SocketGameGateway extends EventEmitter implements IGameGateway { private io: SocketIO.Server; constructor(io: SocketIO.Server) { super(); this.io = io; this.setupSocketHandlers(); } private setupSocketHandlers() { this.io.on('connection', (socket) => { // 处理客户端身份验证(示例:从query获取playerId) socket.data.playerId = socket.handshake.query.playerId as string; socket.join(socket.handshake.query.roomId as string); // 转发客户端动作到应用层 socket.on('player-action', (action: PlayerAction) => { this.emit('player-action', action); }); // 转发断开连接事件 socket.on('disconnect', () => { this.emit('player-disconnect', socket.data.playerId); }); }); } onPlayerAction(callback: (action: PlayerAction) => void): void { this.on('player-action', callback); } pushToPlayer(playerId: string, event: string, data: any): void { this.io.to(playerId).emit(event, data); } broadcastToRoom(roomId: string, event: string, data: any): void { this.io.to(roomId).emit(event, data); } }
2. 应用/服务层(游戏引擎)
// src/service/game.engine.ts import { IGameGateway, PlayerAction } from '../gateway/game.gateway'; export class GameEngine { private roomStates: Record<string, any> = {}; private gateway: IGameGateway; constructor(gateway: IGameGateway) { this.gateway = gateway; // 订阅网关传来的客户端动作 this.gateway.onPlayerAction((action) => this.handlePlayerAction(action)); // 定时更新游戏状态(模拟实时逻辑) setInterval(() => this.syncRoomStates(), 100); } private handlePlayerAction(action: PlayerAction) { const roomId = this.getPlayerRoom(action.playerId); if (!this.roomStates[roomId]) return; // 执行游戏核心逻辑 switch (action.actionType) { case 'move': this.roomStates[roomId].players[action.playerId].position = action.payload.position; break; case 'attack': this.roomStates[roomId].players[action.playerId].attackCount++; break; } } private syncRoomStates() { // 遍历所有房间,推送最新状态 Object.keys(this.roomStates).forEach(roomId => { this.gateway.broadcastToRoom(roomId, 'game-state', this.roomStates[roomId]); }); } private getPlayerRoom(playerId: string): string { // 从存储层获取玩家所在房间(示例逻辑) return 'room-1'; } }
3. 网络层(Express + Socket.io初始化)
// src/server.ts import express from 'express'; import { createServer } from 'http'; import { Server } from 'socket.io'; import { SocketGameGateway } from './gateway/game.gateway'; import { GameEngine } from './service/game.engine'; const app = express(); const httpServer = createServer(app); const io = new Server(httpServer, { cors: { origin: ['https://your-client-domain.com'] } }); // 初始化网关与游戏引擎(依赖注入) const gameGateway = new SocketGameGateway(io); const gameEngine = new GameEngine(gameGateway); // 普通REST路由示例 app.get('/api/rooms', (req, res) => { res.json(Object.keys(gameEngine['roomStates'])); }); httpServer.listen(3000, () => { console.log('Server running on port 3000'); });
关键设计模式建议
- 依赖倒置原则:应用层只依赖
IGameGateway抽象接口,不直接接触Socket.io。后续如果替换为ws或其他WebSocket库,只需新增对应网关实现类,无需修改游戏引擎代码。 - 观察者模式:网关通过
EventEmitter实现事件转发,应用层订阅事件即可处理客户端动作,完全解耦发送者与接收者。 - 端口与适配器模式(六边形架构):将网关视为应用层的"输入/输出端口",
SocketGameGateway是适配Socket.io的适配器,让应用核心逻辑与外部网络实现彻底隔离。
注意事项
- 网络层负责Socket的连接验证、房间管理、错误处理等与传输相关的逻辑,应用层只关心游戏规则。
- 避免在应用层中直接操作Socket实例,所有网络交互必须通过网关接口完成。
- 可以扩展网关接口,增加如
onPlayerDisconnect、pushToAll等方法,满足不同业务场景需求。
内容的提问来源于stack exchange,提问作者pinkboid
相关产品推荐
相关产品推荐

