传输层实现(客户端/服务端,链接管理和心跳机制,重连机制)

消息序列化(json序列化,消息压缩,消息ID和时间戳)
网络服务器核心(networkserver/基础room/链接状态同步)
网络客户端核心(networkclient/消息队列)
This commit is contained in:
YHH
2025-08-14 23:59:00 +08:00
parent 32092f992d
commit 6730a5d625
29 changed files with 8100 additions and 236 deletions

View File

@@ -0,0 +1,410 @@
/**
* 重连管理器
* 负责管理客户端的自动重连逻辑
*/
import { createLogger, ITimer } from '@esengine/ecs-framework';
import { ConnectionState } from '@esengine/network-shared';
import { NetworkTimerManager } from '../utils';
/**
* 重连配置
*/
export interface ReconnectionConfig {
enabled: boolean;
maxAttempts: number;
initialDelay: number;
maxDelay: number;
backoffFactor: number;
jitterEnabled: boolean;
resetAfterSuccess: boolean;
}
/**
* 重连状态
*/
export interface ReconnectionState {
isReconnecting: boolean;
currentAttempt: number;
nextAttemptTime?: number;
lastAttemptTime?: number;
totalAttempts: number;
successfulReconnections: number;
}
/**
* 重连事件接口
*/
export interface ReconnectionEvents {
reconnectStarted: (attempt: number) => void;
reconnectSucceeded: (attempt: number, duration: number) => void;
reconnectFailed: (attempt: number, error: Error) => void;
reconnectAborted: (reason: string) => void;
maxAttemptsReached: () => void;
}
/**
* 重连策略
*/
export enum ReconnectionStrategy {
Exponential = 'exponential',
Linear = 'linear',
Fixed = 'fixed',
Custom = 'custom'
}
/**
* 重连管理器
*/
export class ReconnectionManager {
private logger = createLogger('ReconnectionManager');
private config: ReconnectionConfig;
private state: ReconnectionState;
private eventHandlers: Partial<ReconnectionEvents> = {};
private reconnectTimer?: ITimer;
private reconnectCallback?: () => Promise<void>;
private abortController?: AbortController;
// 策略相关
private strategy: ReconnectionStrategy = ReconnectionStrategy.Exponential;
private customDelayCalculator?: (attempt: number) => number;
/**
* 构造函数
*/
constructor(config: Partial<ReconnectionConfig> = {}) {
this.config = {
enabled: true,
maxAttempts: 10,
initialDelay: 1000, // 1秒
maxDelay: 30000, // 30秒
backoffFactor: 2, // 指数退避因子
jitterEnabled: true, // 启用抖动
resetAfterSuccess: true, // 成功后重置计数
...config
};
this.state = {
isReconnecting: false,
currentAttempt: 0,
totalAttempts: 0,
successfulReconnections: 0
};
}
/**
* 设置重连回调
*/
setReconnectCallback(callback: () => Promise<void>): void {
this.reconnectCallback = callback;
}
/**
* 开始重连
*/
startReconnection(): void {
if (!this.config.enabled) {
this.logger.info('重连已禁用');
return;
}
if (this.state.isReconnecting) {
this.logger.warn('重连已在进行中');
return;
}
if (!this.reconnectCallback) {
this.logger.error('重连回调未设置');
return;
}
// 检查是否达到最大重连次数
if (this.state.currentAttempt >= this.config.maxAttempts) {
this.logger.error(`已达到最大重连次数: ${this.config.maxAttempts}`);
this.eventHandlers.maxAttemptsReached?.();
return;
}
this.state.isReconnecting = true;
this.state.currentAttempt++;
this.state.totalAttempts++;
const delay = this.calculateDelay();
this.state.nextAttemptTime = Date.now() + delay;
this.logger.info(`开始重连 (第 ${this.state.currentAttempt}/${this.config.maxAttempts} 次)${delay}ms 后尝试`);
this.eventHandlers.reconnectStarted?.(this.state.currentAttempt);
// 设置重连定时器
this.reconnectTimer = NetworkTimerManager.schedule(
delay / 1000, // 转为秒
false, // 不重复
this,
() => {
this.performReconnection();
}
);
}
/**
* 停止重连
*/
stopReconnection(reason: string = '用户主动停止'): void {
if (!this.state.isReconnecting) {
return;
}
this.clearReconnectTimer();
this.abortController?.abort();
this.state.isReconnecting = false;
this.state.nextAttemptTime = undefined;
this.logger.info(`重连已停止: ${reason}`);
this.eventHandlers.reconnectAborted?.(reason);
}
/**
* 重连成功
*/
onReconnectionSuccess(): void {
if (!this.state.isReconnecting) {
return;
}
const duration = this.state.lastAttemptTime ? Date.now() - this.state.lastAttemptTime : 0;
this.logger.info(`重连成功 (第 ${this.state.currentAttempt} 次尝试,耗时 ${duration}ms)`);
this.state.isReconnecting = false;
this.state.successfulReconnections++;
this.state.nextAttemptTime = undefined;
// 是否重置重连计数
if (this.config.resetAfterSuccess) {
this.state.currentAttempt = 0;
}
this.clearReconnectTimer();
this.eventHandlers.reconnectSucceeded?.(this.state.currentAttempt, duration);
}
/**
* 重连失败
*/
onReconnectionFailure(error: Error): void {
if (!this.state.isReconnecting) {
return;
}
this.logger.warn(`重连失败 (第 ${this.state.currentAttempt} 次尝试):`, error);
this.eventHandlers.reconnectFailed?.(this.state.currentAttempt, error);
// 检查是否还有重连机会
if (this.state.currentAttempt >= this.config.maxAttempts) {
this.logger.error('重连次数已用完');
this.state.isReconnecting = false;
this.eventHandlers.maxAttemptsReached?.();
} else {
// 继续下一次重连
this.startReconnection();
}
}
/**
* 获取重连状态
*/
getState(): ReconnectionState {
return { ...this.state };
}
/**
* 获取重连统计
*/
getStats() {
return {
totalAttempts: this.state.totalAttempts,
successfulReconnections: this.state.successfulReconnections,
currentAttempt: this.state.currentAttempt,
maxAttempts: this.config.maxAttempts,
isReconnecting: this.state.isReconnecting,
nextAttemptTime: this.state.nextAttemptTime,
successRate: this.state.totalAttempts > 0 ?
(this.state.successfulReconnections / this.state.totalAttempts) * 100 : 0
};
}
/**
* 更新配置
*/
updateConfig(newConfig: Partial<ReconnectionConfig>): void {
Object.assign(this.config, newConfig);
this.logger.info('重连配置已更新:', newConfig);
}
/**
* 设置重连策略
*/
setStrategy(strategy: ReconnectionStrategy, customCalculator?: (attempt: number) => number): void {
this.strategy = strategy;
if (strategy === ReconnectionStrategy.Custom && customCalculator) {
this.customDelayCalculator = customCalculator;
}
this.logger.info(`重连策略已设置为: ${strategy}`);
}
/**
* 设置事件处理器
*/
on<K extends keyof ReconnectionEvents>(event: K, handler: ReconnectionEvents[K]): void {
this.eventHandlers[event] = handler;
}
/**
* 移除事件处理器
*/
off<K extends keyof ReconnectionEvents>(event: K): void {
delete this.eventHandlers[event];
}
/**
* 重置重连状态
*/
reset(): void {
this.stopReconnection('状态重置');
this.state = {
isReconnecting: false,
currentAttempt: 0,
totalAttempts: 0,
successfulReconnections: 0
};
this.logger.info('重连状态已重置');
}
/**
* 强制立即重连
*/
forceReconnect(): void {
if (this.state.isReconnecting) {
this.clearReconnectTimer();
this.performReconnection();
} else {
this.startReconnection();
}
}
/**
* 计算重连延迟
*/
private calculateDelay(): number {
let delay: number;
switch (this.strategy) {
case ReconnectionStrategy.Fixed:
delay = this.config.initialDelay;
break;
case ReconnectionStrategy.Linear:
delay = this.config.initialDelay * this.state.currentAttempt;
break;
case ReconnectionStrategy.Exponential:
delay = this.config.initialDelay * Math.pow(this.config.backoffFactor, this.state.currentAttempt - 1);
break;
case ReconnectionStrategy.Custom:
delay = this.customDelayCalculator ?
this.customDelayCalculator(this.state.currentAttempt) :
this.config.initialDelay;
break;
default:
delay = this.config.initialDelay;
}
// 限制最大延迟
delay = Math.min(delay, this.config.maxDelay);
// 添加抖动以避免雷群效应
if (this.config.jitterEnabled) {
const jitter = delay * 0.1 * Math.random(); // 10%的随机抖动
delay += jitter;
}
return Math.round(delay);
}
/**
* 执行重连
*/
private async performReconnection(): Promise<void> {
if (!this.reconnectCallback || !this.state.isReconnecting) {
return;
}
this.state.lastAttemptTime = Date.now();
this.abortController = new AbortController();
try {
await this.reconnectCallback();
// 重连回调成功,等待实际连接建立再调用 onReconnectionSuccess
} catch (error) {
this.onReconnectionFailure(error as Error);
}
}
/**
* 清除重连定时器
*/
private clearReconnectTimer(): void {
if (this.reconnectTimer) {
this.reconnectTimer.stop();
this.reconnectTimer = undefined;
}
}
/**
* 检查是否应该进行重连
*/
shouldReconnect(reason?: string): boolean {
if (!this.config.enabled) {
return false;
}
if (this.state.currentAttempt >= this.config.maxAttempts) {
return false;
}
// 可以根据断开原因决定是否重连
if (reason) {
const noReconnectReasons = ['user_disconnect', 'invalid_credentials', 'banned'];
if (noReconnectReasons.includes(reason)) {
return false;
}
}
return true;
}
/**
* 获取下次重连的倒计时
*/
getTimeUntilNextAttempt(): number {
if (!this.state.nextAttemptTime) {
return 0;
}
return Math.max(0, this.state.nextAttemptTime - Date.now());
}
/**
* 获取重连进度百分比
*/
getProgress(): number {
if (this.config.maxAttempts === 0) {
return 0;
}
return (this.state.currentAttempt / this.config.maxAttempts) * 100;
}
}

View File

@@ -0,0 +1,406 @@
/**
* WebSocket客户端传输层实现
*/
import { createLogger, ITimer } from '@esengine/ecs-framework';
import {
IClientTransport,
IConnectionOptions,
ConnectionState,
IConnectionStats
} from '@esengine/network-shared';
import { NetworkTimerManager } from '../utils';
/**
* WebSocket客户端实现
*/
export class WebSocketClient implements IClientTransport {
private logger = createLogger('WebSocketClient');
private websocket?: WebSocket;
private connectionState: ConnectionState = ConnectionState.Disconnected;
private options: IConnectionOptions = {};
private url = '';
private reconnectTimer?: ITimer;
private reconnectAttempts = 0;
private stats: IConnectionStats;
/**
* 消息接收事件处理器
*/
private messageHandlers: ((data: ArrayBuffer | string) => void)[] = [];
/**
* 连接状态变化事件处理器
*/
private stateChangeHandlers: ((state: ConnectionState) => void)[] = [];
/**
* 错误事件处理器
*/
private errorHandlers: ((error: Error) => void)[] = [];
/**
* 构造函数
*/
constructor() {
this.stats = {
state: ConnectionState.Disconnected,
reconnectCount: 0,
bytesSent: 0,
bytesReceived: 0,
messagesSent: 0,
messagesReceived: 0
};
}
/**
* 连接到服务器
*/
async connect(url: string, options?: IConnectionOptions): Promise<void> {
if (this.connectionState === ConnectionState.Connected) {
this.logger.warn('客户端已连接');
return;
}
this.url = url;
this.options = {
timeout: 10000,
reconnectInterval: 3000,
maxReconnectAttempts: 5,
autoReconnect: true,
protocolVersion: '1.0',
...options
};
return this.connectInternal();
}
/**
* 断开连接
*/
async disconnect(reason?: string): Promise<void> {
this.options.autoReconnect = false; // 禁用自动重连
this.clearReconnectTimer();
if (this.websocket) {
this.websocket.close(1000, reason || '客户端主动断开');
this.websocket = undefined;
}
this.setConnectionState(ConnectionState.Disconnected);
this.logger.info(`客户端断开连接: ${reason || '主动断开'}`);
}
/**
* 发送数据到服务器
*/
send(data: ArrayBuffer | string): void {
if (!this.websocket || this.connectionState !== ConnectionState.Connected) {
this.logger.warn('客户端未连接,无法发送消息');
return;
}
try {
this.websocket.send(data);
this.stats.messagesSent++;
// 估算字节数
const bytes = typeof data === 'string' ? new Blob([data]).size : data.byteLength;
this.stats.bytesSent += bytes;
} catch (error) {
this.logger.error('发送消息失败:', error);
this.handleError(error as Error);
}
}
/**
* 监听服务器消息
*/
onMessage(handler: (data: ArrayBuffer | string) => void): void {
this.messageHandlers.push(handler);
}
/**
* 监听连接状态变化
*/
onConnectionStateChange(handler: (state: ConnectionState) => void): void {
this.stateChangeHandlers.push(handler);
}
/**
* 监听错误事件
*/
onError(handler: (error: Error) => void): void {
this.errorHandlers.push(handler);
}
/**
* 获取连接状态
*/
getConnectionState(): ConnectionState {
return this.connectionState;
}
/**
* 获取连接统计信息
*/
getStats(): IConnectionStats {
return { ...this.stats };
}
/**
* 内部连接实现
*/
private async connectInternal(): Promise<void> {
return new Promise((resolve, reject) => {
try {
this.setConnectionState(ConnectionState.Connecting);
this.logger.info(`连接到服务器: ${this.url}`);
// 检查WebSocket支持
if (typeof WebSocket === 'undefined') {
throw new Error('当前环境不支持WebSocket');
}
this.websocket = new WebSocket(this.url);
this.setupWebSocketEvents(resolve, reject);
// 设置连接超时
if (this.options.timeout) {
NetworkTimerManager.schedule(
this.options.timeout / 1000, // 转为秒
false, // 不重复
this,
() => {
if (this.connectionState === ConnectionState.Connecting) {
this.websocket?.close();
reject(new Error(`连接超时 (${this.options.timeout}ms)`));
}
}
);
}
} catch (error) {
this.logger.error('创建WebSocket连接失败:', error);
this.setConnectionState(ConnectionState.Failed);
reject(error);
}
});
}
/**
* 设置WebSocket事件监听
*/
private setupWebSocketEvents(
resolve: () => void,
reject: (error: Error) => void
): void {
if (!this.websocket) return;
// 连接打开
this.websocket.onopen = () => {
this.setConnectionState(ConnectionState.Connected);
this.stats.connectTime = Date.now();
this.reconnectAttempts = 0; // 重置重连计数
this.logger.info('WebSocket连接已建立');
resolve();
};
// 消息接收
this.websocket.onmessage = (event) => {
this.handleMessage(event.data);
};
// 连接关闭
this.websocket.onclose = (event) => {
this.handleConnectionClose(event.code, event.reason);
};
// 错误处理
this.websocket.onerror = (event) => {
const error = new Error(`WebSocket错误: ${event}`);
this.logger.error('WebSocket错误:', error);
this.handleError(error);
if (this.connectionState === ConnectionState.Connecting) {
reject(error);
}
};
}
/**
* 处理接收到的消息
*/
private handleMessage(data: any): void {
try {
this.stats.messagesReceived++;
// 估算字节数
const bytes = typeof data === 'string' ? new Blob([data]).size : data.byteLength || 0;
this.stats.bytesReceived += bytes;
// 触发消息事件
this.messageHandlers.forEach(handler => {
try {
handler(data);
} catch (error) {
this.logger.error('消息事件处理器错误:', error);
}
});
} catch (error) {
this.logger.error('处理接收消息失败:', error);
this.handleError(error as Error);
}
}
/**
* 处理连接关闭
*/
private handleConnectionClose(code: number, reason: string): void {
this.stats.disconnectTime = Date.now();
this.websocket = undefined;
this.logger.info(`WebSocket连接已关闭: code=${code}, reason=${reason}`);
// 根据关闭代码决定是否重连
const shouldReconnect = this.shouldReconnect(code);
if (shouldReconnect && this.options.autoReconnect) {
this.setConnectionState(ConnectionState.Reconnecting);
this.scheduleReconnect();
} else {
this.setConnectionState(ConnectionState.Disconnected);
}
}
/**
* 判断是否应该重连
*/
private shouldReconnect(closeCode: number): boolean {
// 正常关闭1000或服务器重启1001时应该重连
// 协议错误1002-1003、数据格式错误1007等不应重连
const reconnectableCodes = [1000, 1001, 1006, 1011];
return reconnectableCodes.includes(closeCode);
}
/**
* 安排重连
*/
private scheduleReconnect(): void {
if (this.reconnectAttempts >= (this.options.maxReconnectAttempts || 5)) {
this.logger.error(`达到最大重连次数 (${this.reconnectAttempts})`);
this.setConnectionState(ConnectionState.Failed);
return;
}
// 指数退避算法
const delay = Math.min(
this.options.reconnectInterval! * Math.pow(2, this.reconnectAttempts),
30000 // 最大30秒
);
this.logger.info(`${delay}ms 后尝试重连 (第 ${this.reconnectAttempts + 1} 次)`);
this.reconnectTimer = NetworkTimerManager.schedule(
delay / 1000, // 转为秒
false, // 不重复
this,
() => {
this.reconnectAttempts++;
this.stats.reconnectCount++;
this.connectInternal().catch(error => {
this.logger.error(`重连失败 (第 ${this.reconnectAttempts} 次):`, error);
this.scheduleReconnect();
});
}
);
}
/**
* 清除重连定时器
*/
private clearReconnectTimer(): void {
if (this.reconnectTimer) {
this.reconnectTimer.stop();
this.reconnectTimer = undefined;
}
}
/**
* 设置连接状态
*/
private setConnectionState(state: ConnectionState): void {
if (this.connectionState === state) return;
const oldState = this.connectionState;
this.connectionState = state;
this.stats.state = state;
this.logger.debug(`连接状态变化: ${oldState} -> ${state}`);
// 触发状态变化事件
this.stateChangeHandlers.forEach(handler => {
try {
handler(state);
} 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 ping(): void {
if (this.websocket && this.connectionState === ConnectionState.Connected) {
// WebSocket的ping/pong由浏览器自动处理
// 这里可以发送应用层心跳消息
this.send(JSON.stringify({ type: 'ping', timestamp: Date.now() }));
}
}
/**
* 手动触发重连
*/
public reconnect(): void {
if (this.connectionState === ConnectionState.Disconnected ||
this.connectionState === ConnectionState.Failed) {
this.reconnectAttempts = 0;
this.connectInternal().catch(error => {
this.logger.error('手动重连失败:', error);
});
}
}
/**
* 获取延迟信息(简单实现)
*/
public getLatency(): number | undefined {
return this.stats.latency;
}
/**
* 销毁客户端
*/
public destroy(): void {
this.disconnect('客户端销毁');
this.messageHandlers.length = 0;
this.stateChangeHandlers.length = 0;
this.errorHandlers.length = 0;
}
}