传输层实现(客户端/服务端,链接管理和心跳机制,重连机制)
消息序列化(json序列化,消息压缩,消息ID和时间戳) 网络服务器核心(networkserver/基础room/链接状态同步) 网络客户端核心(networkclient/消息队列)
This commit is contained in:
407
packages/network-server/src/transport/WebSocketTransport.ts
Normal file
407
packages/network-server/src/transport/WebSocketTransport.ts
Normal file
@@ -0,0 +1,407 @@
|
||||
/**
|
||||
* WebSocket传输层服务端实现
|
||||
*/
|
||||
import WebSocket, { WebSocketServer } from 'ws';
|
||||
import { createLogger, Core } from '@esengine/ecs-framework';
|
||||
import {
|
||||
ITransport,
|
||||
ITransportClientInfo,
|
||||
ITransportConfig,
|
||||
ConnectionState,
|
||||
EventEmitter
|
||||
} from '@esengine/network-shared';
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
/**
|
||||
* WebSocket传输层实现
|
||||
*/
|
||||
export class WebSocketTransport extends EventEmitter implements ITransport {
|
||||
private logger = createLogger('WebSocketTransport');
|
||||
private server?: WebSocketServer;
|
||||
private clients: Map<string, WebSocket> = new Map();
|
||||
private clientInfo: Map<string, ITransportClientInfo> = new Map();
|
||||
private config: ITransportConfig;
|
||||
private isRunning = false;
|
||||
|
||||
/**
|
||||
* 连接事件处理器
|
||||
*/
|
||||
private connectHandlers: ((clientInfo: ITransportClientInfo) => void)[] = [];
|
||||
|
||||
/**
|
||||
* 断开连接事件处理器
|
||||
*/
|
||||
private disconnectHandlers: ((clientId: string, reason?: string) => void)[] = [];
|
||||
|
||||
/**
|
||||
* 消息接收事件处理器
|
||||
*/
|
||||
private messageHandlers: ((clientId: string, data: ArrayBuffer | string) => void)[] = [];
|
||||
|
||||
/**
|
||||
* 错误事件处理器
|
||||
*/
|
||||
private errorHandlers: ((error: Error) => void)[] = [];
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
constructor(config: ITransportConfig) {
|
||||
super();
|
||||
this.config = {
|
||||
maxConnections: 1000,
|
||||
heartbeatInterval: 30000,
|
||||
connectionTimeout: 60000,
|
||||
maxMessageSize: 1024 * 1024, // 1MB
|
||||
compression: true,
|
||||
...config
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动传输层
|
||||
*/
|
||||
async start(port: number, host?: string): Promise<void> {
|
||||
if (this.isRunning) {
|
||||
this.logger.warn('WebSocket传输层已在运行');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.server = new WebSocketServer({
|
||||
port,
|
||||
host: host || '0.0.0.0',
|
||||
maxPayload: this.config.maxMessageSize,
|
||||
perMessageDeflate: this.config.compression ? {
|
||||
threshold: 1024,
|
||||
concurrencyLimit: 10,
|
||||
clientNoContextTakeover: false,
|
||||
serverNoContextTakeover: false
|
||||
} : false
|
||||
});
|
||||
|
||||
this.setupServerEvents();
|
||||
this.isRunning = true;
|
||||
|
||||
this.logger.info(`WebSocket服务器已启动: ${host || '0.0.0.0'}:${port}`);
|
||||
this.logger.info(`最大连接数: ${this.config.maxConnections}`);
|
||||
this.logger.info(`压缩: ${this.config.compression ? '启用' : '禁用'}`);
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error('启动WebSocket服务器失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止传输层
|
||||
*/
|
||||
async stop(): Promise<void> {
|
||||
if (!this.isRunning || !this.server) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
// 断开所有客户端连接
|
||||
for (const [clientId, ws] of this.clients) {
|
||||
ws.close(1001, '服务器关闭');
|
||||
this.handleClientDisconnect(clientId, '服务器关闭');
|
||||
}
|
||||
|
||||
// 关闭服务器
|
||||
this.server!.close(() => {
|
||||
this.isRunning = false;
|
||||
this.server = undefined;
|
||||
this.logger.info('WebSocket服务器已停止');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送数据到指定客户端
|
||||
*/
|
||||
send(clientId: string, data: ArrayBuffer | string): void {
|
||||
const ws = this.clients.get(clientId);
|
||||
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
||||
this.logger.warn(`尝试向未连接的客户端发送消息: ${clientId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ws.send(data);
|
||||
} catch (error) {
|
||||
this.logger.error(`发送消息到客户端 ${clientId} 失败:`, error);
|
||||
this.handleError(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 广播数据到所有客户端
|
||||
*/
|
||||
broadcast(data: ArrayBuffer | string, exclude?: string[]): void {
|
||||
const excludeSet = new Set(exclude || []);
|
||||
|
||||
for (const [clientId, ws] of this.clients) {
|
||||
if (excludeSet.has(clientId) || ws.readyState !== WebSocket.OPEN) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
ws.send(data);
|
||||
} catch (error) {
|
||||
this.logger.error(`广播消息到客户端 ${clientId} 失败:`, error);
|
||||
this.handleError(error as Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听客户端连接事件
|
||||
*/
|
||||
onConnect(handler: (clientInfo: ITransportClientInfo) => void): void {
|
||||
this.connectHandlers.push(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听客户端断开事件
|
||||
*/
|
||||
onDisconnect(handler: (clientId: string, reason?: string) => void): void {
|
||||
this.disconnectHandlers.push(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听消息接收事件
|
||||
*/
|
||||
onMessage(handler: (clientId: string, data: ArrayBuffer | string) => void): void {
|
||||
this.messageHandlers.push(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听错误事件
|
||||
*/
|
||||
onError(handler: (error: Error) => void): void {
|
||||
this.errorHandlers.push(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接的客户端数量
|
||||
*/
|
||||
getClientCount(): number {
|
||||
return this.clients.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查客户端是否连接
|
||||
*/
|
||||
isClientConnected(clientId: string): boolean {
|
||||
const ws = this.clients.get(clientId);
|
||||
return ws !== undefined && ws.readyState === WebSocket.OPEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开指定客户端
|
||||
*/
|
||||
disconnectClient(clientId: string, reason?: string): void {
|
||||
const ws = this.clients.get(clientId);
|
||||
if (ws) {
|
||||
ws.close(1000, reason || '服务器主动断开');
|
||||
this.handleClientDisconnect(clientId, reason);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端信息
|
||||
*/
|
||||
getClientInfo(clientId: string): ITransportClientInfo | undefined {
|
||||
return this.clientInfo.get(clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有客户端信息
|
||||
*/
|
||||
getAllClients(): ITransportClientInfo[] {
|
||||
return Array.from(this.clientInfo.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置服务器事件监听
|
||||
*/
|
||||
private setupServerEvents(): void {
|
||||
if (!this.server) return;
|
||||
|
||||
this.server.on('connection', (ws, request) => {
|
||||
this.handleNewConnection(ws, request);
|
||||
});
|
||||
|
||||
this.server.on('error', (error) => {
|
||||
this.logger.error('WebSocket服务器错误:', error);
|
||||
this.handleError(error);
|
||||
});
|
||||
|
||||
this.server.on('close', () => {
|
||||
this.logger.info('WebSocket服务器已关闭');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理新客户端连接
|
||||
*/
|
||||
private handleNewConnection(ws: WebSocket, request: any): void {
|
||||
// 检查连接数限制
|
||||
if (this.clients.size >= this.config.maxConnections!) {
|
||||
this.logger.warn('达到最大连接数限制,拒绝新连接');
|
||||
ws.close(1013, '服务器繁忙');
|
||||
return;
|
||||
}
|
||||
|
||||
const clientId = crypto.randomUUID();
|
||||
const clientInfo: ITransportClientInfo = {
|
||||
id: clientId,
|
||||
remoteAddress: request.socket.remoteAddress || 'unknown',
|
||||
connectTime: Date.now(),
|
||||
userAgent: request.headers['user-agent'],
|
||||
headers: request.headers
|
||||
};
|
||||
|
||||
// 存储客户端连接和信息
|
||||
this.clients.set(clientId, ws);
|
||||
this.clientInfo.set(clientId, clientInfo);
|
||||
|
||||
// 设置WebSocket事件监听
|
||||
this.setupClientEvents(ws, clientId);
|
||||
|
||||
this.logger.info(`新客户端连接: ${clientId} from ${clientInfo.remoteAddress}`);
|
||||
|
||||
// 触发连接事件
|
||||
this.connectHandlers.forEach(handler => {
|
||||
try {
|
||||
handler(clientInfo);
|
||||
} catch (error) {
|
||||
this.logger.error('连接事件处理器错误:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置客户端WebSocket事件监听
|
||||
*/
|
||||
private setupClientEvents(ws: WebSocket, clientId: string): void {
|
||||
// 消息接收
|
||||
ws.on('message', (data) => {
|
||||
this.handleClientMessage(clientId, data);
|
||||
});
|
||||
|
||||
// 连接关闭
|
||||
ws.on('close', (code, reason) => {
|
||||
this.handleClientDisconnect(clientId, reason?.toString() || `Code: ${code}`);
|
||||
});
|
||||
|
||||
// 错误处理
|
||||
ws.on('error', (error) => {
|
||||
this.logger.error(`客户端 ${clientId} WebSocket错误:`, error);
|
||||
this.handleError(error);
|
||||
});
|
||||
|
||||
// Pong响应(心跳)
|
||||
ws.on('pong', () => {
|
||||
// 记录客户端响应心跳
|
||||
const info = this.clientInfo.get(clientId);
|
||||
if (info) {
|
||||
// 可以更新延迟信息
|
||||
}
|
||||
});
|
||||
|
||||
// 设置连接超时
|
||||
if (this.config.connectionTimeout) {
|
||||
Core.schedule(this.config.connectionTimeout / 1000, false, this, () => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.ping();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理客户端消息
|
||||
*/
|
||||
private handleClientMessage(clientId: string, data: WebSocket.Data): void {
|
||||
try {
|
||||
const message = data instanceof ArrayBuffer ? data : new TextEncoder().encode(data.toString()).buffer;
|
||||
|
||||
// 触发消息事件
|
||||
this.messageHandlers.forEach(handler => {
|
||||
try {
|
||||
handler(clientId, message);
|
||||
} catch (error) {
|
||||
this.logger.error('消息事件处理器错误:', error);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error(`处理客户端 ${clientId} 消息失败:`, error);
|
||||
this.handleError(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理客户端断开连接
|
||||
*/
|
||||
private handleClientDisconnect(clientId: string, reason?: string): void {
|
||||
// 清理客户端数据
|
||||
this.clients.delete(clientId);
|
||||
this.clientInfo.delete(clientId);
|
||||
|
||||
this.logger.info(`客户端断开连接: ${clientId}, 原因: ${reason || '未知'}`);
|
||||
|
||||
// 触发断开连接事件
|
||||
this.disconnectHandlers.forEach(handler => {
|
||||
try {
|
||||
handler(clientId, reason);
|
||||
} catch (error) {
|
||||
this.logger.error('断开连接事件处理器错误:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理错误
|
||||
*/
|
||||
private handleError(error: Error): void {
|
||||
this.errorHandlers.forEach(handler => {
|
||||
try {
|
||||
handler(error);
|
||||
} catch (handlerError) {
|
||||
this.logger.error('错误事件处理器错误:', handlerError);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送心跳到所有客户端
|
||||
*/
|
||||
public sendHeartbeat(): void {
|
||||
for (const [clientId, ws] of this.clients) {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
ws.ping();
|
||||
} catch (error) {
|
||||
this.logger.error(`发送心跳到客户端 ${clientId} 失败:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取传输层统计信息
|
||||
*/
|
||||
public getStats() {
|
||||
return {
|
||||
isRunning: this.isRunning,
|
||||
clientCount: this.clients.size,
|
||||
maxConnections: this.config.maxConnections,
|
||||
compressionEnabled: this.config.compression,
|
||||
clients: this.getAllClients()
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user