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

消息序列化(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

@@ -1,47 +0,0 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom', // 客户端使用jsdom环境
roots: ['<rootDir>/tests'],
testMatch: ['**/*.test.ts', '**/*.spec.ts'],
testPathIgnorePatterns: ['/node_modules/'],
collectCoverage: false,
collectCoverageFrom: [
'src/**/*.ts',
'!src/index.ts',
'!src/**/index.ts',
'!**/*.d.ts',
'!src/**/*.test.ts',
'!src/**/*.spec.ts'
],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
coverageThreshold: {
global: {
branches: 70,
functions: 70,
lines: 70,
statements: 70
}
},
verbose: true,
transform: {
'^.+\\.tsx?$': ['ts-jest', {
tsconfig: 'tsconfig.json',
useESM: false,
}],
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
testTimeout: 10000,
clearMocks: true,
restoreMocks: true,
modulePathIgnorePatterns: [
'<rootDir>/bin/',
'<rootDir>/dist/',
'<rootDir>/node_modules/'
]
};

View File

@@ -42,10 +42,7 @@
"publish:minor": "npm version minor && npm run build:npm && cd dist && npm publish",
"publish:major": "npm version major && npm run build:npm && cd dist && npm publish",
"preversion": "npm run rebuild",
"test": "jest --config jest.config.cjs",
"test:watch": "jest --watch --config jest.config.cjs",
"test:coverage": "jest --coverage --config jest.config.cjs",
"test:ci": "jest --ci --coverage --config jest.config.cjs"
"test": "echo \"No tests configured for network-client\""
},
"author": "yhh",
"license": "MIT",

View File

@@ -0,0 +1,638 @@
/**
* 连接状态管理器
* 负责跟踪连接状态变化、状态变化通知和自动恢复逻辑
*/
import { createLogger, ITimer } from '@esengine/ecs-framework';
import { ConnectionState, IConnectionStats, EventEmitter } from '@esengine/network-shared';
import { NetworkTimerManager } from '../utils';
/**
* 状态转换规则
*/
export interface StateTransitionRule {
from: ConnectionState;
to: ConnectionState;
condition?: () => boolean;
action?: () => void;
}
/**
* 连接状态管理器配置
*/
export interface ConnectionStateManagerConfig {
enableAutoRecovery: boolean;
recoveryTimeout: number;
maxRecoveryAttempts: number;
stateTimeout: number;
enableStateValidation: boolean;
logStateChanges: boolean;
}
/**
* 状态历史记录
*/
export interface StateHistoryEntry {
state: ConnectionState;
timestamp: number;
duration?: number;
reason?: string;
metadata?: Record<string, any>;
}
/**
* 状态统计信息
*/
export interface StateStats {
currentState: ConnectionState;
totalTransitions: number;
stateHistory: StateHistoryEntry[];
stateDurations: Record<ConnectionState, number[]>;
averageStateDurations: Record<ConnectionState, number>;
totalUptime: number;
totalDowntime: number;
connectionAttempts: number;
successfulConnections: number;
failedConnections: number;
}
/**
* 连接状态管理器事件接口
*/
export interface ConnectionStateManagerEvents {
stateChanged: (oldState: ConnectionState, newState: ConnectionState, reason?: string) => void;
stateTimeout: (state: ConnectionState, duration: number) => void;
recoveryStarted: (attempt: number) => void;
recoverySucceeded: () => void;
recoveryFailed: (maxAttemptsReached: boolean) => void;
invalidTransition: (from: ConnectionState, to: ConnectionState) => void;
}
/**
* 连接状态管理器
*/
export class ConnectionStateManager extends EventEmitter {
private logger = createLogger('ConnectionStateManager');
private config: ConnectionStateManagerConfig;
private currentState: ConnectionState = ConnectionState.Disconnected;
private previousState?: ConnectionState;
private stateStartTime: number = Date.now();
private stats: StateStats;
// 状态管理
private stateHistory: StateHistoryEntry[] = [];
private transitionRules: StateTransitionRule[] = [];
private stateTimeouts: Map<ConnectionState, ITimer> = new Map();
// 恢复逻辑
private recoveryAttempts = 0;
private recoveryTimer?: ITimer;
// Timer管理器使用静态的NetworkTimerManager
private recoveryCallback?: () => Promise<void>;
// 事件处理器
private eventHandlers: Partial<ConnectionStateManagerEvents> = {};
/**
* 构造函数
*/
constructor(config: Partial<ConnectionStateManagerConfig> = {}) {
super();
this.config = {
enableAutoRecovery: true,
recoveryTimeout: 5000,
maxRecoveryAttempts: 3,
stateTimeout: 30000,
enableStateValidation: true,
logStateChanges: true,
...config
};
this.stats = {
currentState: this.currentState,
totalTransitions: 0,
stateHistory: [],
stateDurations: {
[ConnectionState.Disconnected]: [],
[ConnectionState.Connecting]: [],
[ConnectionState.Connected]: [],
[ConnectionState.Reconnecting]: [],
[ConnectionState.Failed]: []
},
averageStateDurations: {
[ConnectionState.Disconnected]: 0,
[ConnectionState.Connecting]: 0,
[ConnectionState.Connected]: 0,
[ConnectionState.Reconnecting]: 0,
[ConnectionState.Failed]: 0
},
totalUptime: 0,
totalDowntime: 0,
connectionAttempts: 0,
successfulConnections: 0,
failedConnections: 0
};
this.initializeDefaultTransitionRules();
}
/**
* 设置当前状态
*/
setState(newState: ConnectionState, reason?: string, metadata?: Record<string, any>): boolean {
if (this.currentState === newState) {
return true;
}
// 验证状态转换
if (this.config.enableStateValidation && !this.isValidTransition(this.currentState, newState)) {
this.logger.warn(`无效的状态转换: ${this.currentState} -> ${newState}`);
this.eventHandlers.invalidTransition?.(this.currentState, newState);
return false;
}
const oldState = this.currentState;
const now = Date.now();
const duration = now - this.stateStartTime;
// 更新状态历史
this.updateStateHistory(oldState, duration, reason, metadata);
// 清理旧状态的超时定时器
this.clearStateTimeout(oldState);
// 更新当前状态
this.previousState = oldState;
this.currentState = newState;
this.stateStartTime = now;
this.stats.currentState = newState;
this.stats.totalTransitions++;
// 更新连接统计
this.updateConnectionStats(oldState, newState);
if (this.config.logStateChanges) {
this.logger.info(`连接状态变化: ${oldState} -> ${newState}${reason ? ` (${reason})` : ''}`);
}
// 设置新状态的超时定时器
this.setStateTimeout(newState);
// 处理自动恢复逻辑
this.handleAutoRecovery(newState);
// 触发事件
this.eventHandlers.stateChanged?.(oldState, newState, reason);
this.emit('stateChanged', oldState, newState, reason);
return true;
}
/**
* 获取当前状态
*/
getState(): ConnectionState {
return this.currentState;
}
/**
* 获取上一个状态
*/
getPreviousState(): ConnectionState | undefined {
return this.previousState;
}
/**
* 获取当前状态持续时间
*/
getCurrentStateDuration(): number {
return Date.now() - this.stateStartTime;
}
/**
* 获取状态统计信息
*/
getStats(): StateStats {
this.updateCurrentStats();
return {
...this.stats,
stateHistory: [...this.stateHistory]
};
}
/**
* 重置统计信息
*/
resetStats(): void {
this.stats = {
currentState: this.currentState,
totalTransitions: 0,
stateHistory: [],
stateDurations: {
[ConnectionState.Disconnected]: [],
[ConnectionState.Connecting]: [],
[ConnectionState.Connected]: [],
[ConnectionState.Reconnecting]: [],
[ConnectionState.Failed]: []
},
averageStateDurations: {
[ConnectionState.Disconnected]: 0,
[ConnectionState.Connecting]: 0,
[ConnectionState.Connected]: 0,
[ConnectionState.Reconnecting]: 0,
[ConnectionState.Failed]: 0
},
totalUptime: 0,
totalDowntime: 0,
connectionAttempts: 0,
successfulConnections: 0,
failedConnections: 0
};
this.stateHistory.length = 0;
this.recoveryAttempts = 0;
}
/**
* 设置恢复回调
*/
setRecoveryCallback(callback: () => Promise<void>): void {
this.recoveryCallback = callback;
}
/**
* 添加状态转换规则
*/
addTransitionRule(rule: StateTransitionRule): void {
this.transitionRules.push(rule);
}
/**
* 移除状态转换规则
*/
removeTransitionRule(from: ConnectionState, to: ConnectionState): boolean {
const index = this.transitionRules.findIndex(rule => rule.from === from && rule.to === to);
if (index >= 0) {
this.transitionRules.splice(index, 1);
return true;
}
return false;
}
/**
* 检查状态转换是否有效
*/
isValidTransition(from: ConnectionState, to: ConnectionState): boolean {
// 检查自定义转换规则
const rule = this.transitionRules.find(r => r.from === from && r.to === to);
if (rule) {
return rule.condition ? rule.condition() : true;
}
// 默认转换规则
return this.isDefaultValidTransition(from, to);
}
/**
* 检查是否为连接状态
*/
isConnected(): boolean {
return this.currentState === ConnectionState.Connected;
}
/**
* 检查是否为连接中状态
*/
isConnecting(): boolean {
return this.currentState === ConnectionState.Connecting ||
this.currentState === ConnectionState.Reconnecting;
}
/**
* 检查是否为断开连接状态
*/
isDisconnected(): boolean {
return this.currentState === ConnectionState.Disconnected ||
this.currentState === ConnectionState.Failed;
}
/**
* 手动触发恢复
*/
triggerRecovery(): void {
if (this.config.enableAutoRecovery && this.recoveryCallback) {
this.startRecovery();
}
}
/**
* 停止自动恢复
*/
stopRecovery(): void {
if (this.recoveryTimer) {
this.recoveryTimer.stop();
this.recoveryTimer = undefined;
}
this.recoveryAttempts = 0;
}
/**
* 设置事件处理器
*/
override on<K extends keyof ConnectionStateManagerEvents>(event: K, handler: ConnectionStateManagerEvents[K]): this {
this.eventHandlers[event] = handler;
return super.on(event, handler as any);
}
/**
* 移除事件处理器
*/
override off<K extends keyof ConnectionStateManagerEvents>(event: K): this {
delete this.eventHandlers[event];
return super.off(event, this.eventHandlers[event] as any);
}
/**
* 更新配置
*/
updateConfig(newConfig: Partial<ConnectionStateManagerConfig>): void {
Object.assign(this.config, newConfig);
this.logger.info('连接状态管理器配置已更新:', newConfig);
}
/**
* 销毁管理器
*/
destroy(): void {
this.stopRecovery();
this.clearAllStateTimeouts();
this.removeAllListeners();
}
/**
* 初始化默认转换规则
*/
private initializeDefaultTransitionRules(): void {
// 连接尝试
this.addTransitionRule({
from: ConnectionState.Disconnected,
to: ConnectionState.Connecting,
action: () => this.stats.connectionAttempts++
});
// 连接成功
this.addTransitionRule({
from: ConnectionState.Connecting,
to: ConnectionState.Connected,
action: () => {
this.stats.successfulConnections++;
this.recoveryAttempts = 0; // 重置恢复计数
}
});
// 连接失败
this.addTransitionRule({
from: ConnectionState.Connecting,
to: ConnectionState.Failed,
action: () => this.stats.failedConnections++
});
// 重连尝试
this.addTransitionRule({
from: ConnectionState.Failed,
to: ConnectionState.Reconnecting,
action: () => this.stats.connectionAttempts++
});
// 重连成功
this.addTransitionRule({
from: ConnectionState.Reconnecting,
to: ConnectionState.Connected,
action: () => {
this.stats.successfulConnections++;
this.recoveryAttempts = 0;
}
});
}
/**
* 检查默认的有效转换
*/
private isDefaultValidTransition(from: ConnectionState, to: ConnectionState): boolean {
const validTransitions: Record<ConnectionState, ConnectionState[]> = {
[ConnectionState.Disconnected]: [ConnectionState.Connecting],
[ConnectionState.Connecting]: [ConnectionState.Connected, ConnectionState.Failed, ConnectionState.Disconnected],
[ConnectionState.Connected]: [ConnectionState.Disconnected, ConnectionState.Failed, ConnectionState.Reconnecting],
[ConnectionState.Reconnecting]: [ConnectionState.Connected, ConnectionState.Failed, ConnectionState.Disconnected],
[ConnectionState.Failed]: [ConnectionState.Reconnecting, ConnectionState.Connecting, ConnectionState.Disconnected]
};
return validTransitions[from]?.includes(to) || false;
}
/**
* 更新状态历史
*/
private updateStateHistory(state: ConnectionState, duration: number, reason?: string, metadata?: Record<string, any>): void {
const entry: StateHistoryEntry = {
state,
timestamp: this.stateStartTime,
duration,
reason,
metadata
};
this.stateHistory.push(entry);
// 限制历史记录数量
if (this.stateHistory.length > 100) {
this.stateHistory.shift();
}
// 更新状态持续时间统计
this.stats.stateDurations[state].push(duration);
if (this.stats.stateDurations[state].length > 50) {
this.stats.stateDurations[state].shift();
}
// 计算平均持续时间
const durations = this.stats.stateDurations[state];
this.stats.averageStateDurations[state] =
durations.reduce((sum, d) => sum + d, 0) / durations.length;
}
/**
* 更新连接统计
*/
private updateConnectionStats(from: ConnectionState, to: ConnectionState): void {
const now = Date.now();
const duration = now - this.stateStartTime;
// 更新在线/离线时间
if (from === ConnectionState.Connected) {
this.stats.totalUptime += duration;
} else if (this.isConnected()) {
// 进入连接状态
}
if (this.isDisconnected() && !this.wasDisconnected(from)) {
// 进入断开状态,开始计算离线时间
} else if (!this.isDisconnected() && this.wasDisconnected(from)) {
// 离开断开状态
this.stats.totalDowntime += duration;
}
}
/**
* 检查之前是否为断开状态
*/
private wasDisconnected(state: ConnectionState): boolean {
return state === ConnectionState.Disconnected || state === ConnectionState.Failed;
}
/**
* 设置状态超时定时器
*/
private setStateTimeout(state: ConnectionState): void {
if (this.config.stateTimeout <= 0) {
return;
}
const timeout = NetworkTimerManager.schedule(
this.config.stateTimeout / 1000, // 转为秒
false, // 不重复
this,
() => {
const duration = this.getCurrentStateDuration();
this.logger.warn(`状态超时: ${state}, 持续时间: ${duration}ms`);
this.eventHandlers.stateTimeout?.(state, duration);
}
);
this.stateTimeouts.set(state, timeout);
}
/**
* 清理状态超时定时器
*/
private clearStateTimeout(state: ConnectionState): void {
const timeout = this.stateTimeouts.get(state);
if (timeout) {
timeout.stop();
this.stateTimeouts.delete(state);
}
}
/**
* 清理所有状态超时定时器
*/
private clearAllStateTimeouts(): void {
for (const timeout of this.stateTimeouts.values()) {
timeout.stop();
}
this.stateTimeouts.clear();
}
/**
* 处理自动恢复逻辑
*/
private handleAutoRecovery(newState: ConnectionState): void {
if (!this.config.enableAutoRecovery) {
return;
}
// 检查是否需要开始恢复
if (newState === ConnectionState.Failed || newState === ConnectionState.Disconnected) {
if (this.previousState === ConnectionState.Connected || this.previousState === ConnectionState.Connecting) {
this.startRecovery();
}
}
}
/**
* 开始恢复过程
*/
private startRecovery(): void {
if (this.recoveryAttempts >= this.config.maxRecoveryAttempts) {
this.logger.warn(`已达到最大恢复尝试次数: ${this.config.maxRecoveryAttempts}`);
this.eventHandlers.recoveryFailed?.(true);
return;
}
if (!this.recoveryCallback) {
this.logger.error('恢复回调未设置');
return;
}
this.recoveryAttempts++;
this.logger.info(`开始自动恢复 (第 ${this.recoveryAttempts} 次)`);
this.eventHandlers.recoveryStarted?.(this.recoveryAttempts);
this.recoveryTimer = NetworkTimerManager.schedule(
this.config.recoveryTimeout / 1000, // 转为秒
false, // 不重复
this,
async () => {
try {
await this.recoveryCallback!();
this.eventHandlers.recoverySucceeded?.();
} catch (error) {
this.logger.error(`自动恢复失败 (第 ${this.recoveryAttempts} 次):`, error);
this.eventHandlers.recoveryFailed?.(false);
// 继续尝试恢复
this.startRecovery();
}
}
);
}
/**
* 更新当前统计信息
*/
private updateCurrentStats(): void {
const now = Date.now();
const currentDuration = now - this.stateStartTime;
if (this.currentState === ConnectionState.Connected) {
this.stats.totalUptime += currentDuration - (this.stats.totalUptime > 0 ? 0 : currentDuration);
}
}
/**
* 获取状态可读名称
*/
getStateDisplayName(state?: ConnectionState): string {
const stateNames: Record<ConnectionState, string> = {
[ConnectionState.Disconnected]: '已断开',
[ConnectionState.Connecting]: '连接中',
[ConnectionState.Connected]: '已连接',
[ConnectionState.Reconnecting]: '重连中',
[ConnectionState.Failed]: '连接失败'
};
return stateNames[state || this.currentState];
}
/**
* 获取连接质量评级
*/
getConnectionQuality(): 'excellent' | 'good' | 'fair' | 'poor' {
const successRate = this.stats.connectionAttempts > 0 ?
this.stats.successfulConnections / this.stats.connectionAttempts : 0;
const averageConnectedTime = this.stats.averageStateDurations[ConnectionState.Connected];
if (successRate > 0.9 && averageConnectedTime > 60000) { // 成功率>90%且平均连接时间>1分钟
return 'excellent';
} else if (successRate > 0.7 && averageConnectedTime > 30000) {
return 'good';
} else if (successRate > 0.5 && averageConnectedTime > 10000) {
return 'fair';
} else {
return 'poor';
}
}
}

View File

@@ -0,0 +1,680 @@
/**
* 消息队列管理器
* 提供消息排队、优先级处理和可靠传输保证
*/
import { createLogger, ITimer } from '@esengine/ecs-framework';
import { INetworkMessage, MessageType } from '@esengine/network-shared';
import { NetworkTimerManager } from '../utils';
/**
* 消息优先级
*/
export enum MessagePriority {
Low = 1,
Normal = 5,
High = 8,
Critical = 10
}
/**
* 队列消息包装器
*/
export interface QueuedMessage {
id: string;
message: INetworkMessage;
priority: MessagePriority;
timestamp: number;
retryCount: number;
maxRetries: number;
reliable: boolean;
timeoutMs?: number;
callback?: (success: boolean, error?: Error) => void;
}
/**
* 消息队列配置
*/
export interface MessageQueueConfig {
maxQueueSize: number;
maxRetries: number;
retryDelay: number;
processingInterval: number;
enablePriority: boolean;
enableReliableDelivery: boolean;
defaultTimeout: number;
}
/**
* 队列统计信息
*/
export interface QueueStats {
totalQueued: number;
totalProcessed: number;
totalFailed: number;
currentSize: number;
averageProcessingTime: number;
messagesByPriority: Record<MessagePriority, number>;
reliableMessages: number;
expiredMessages: number;
}
/**
* 消息队列事件接口
*/
export interface MessageQueueEvents {
messageQueued: (message: QueuedMessage) => void;
messageProcessed: (message: QueuedMessage, success: boolean) => void;
messageFailed: (message: QueuedMessage, error: Error) => void;
messageExpired: (message: QueuedMessage) => void;
queueFull: (droppedMessage: QueuedMessage) => void;
}
/**
* 消息队列管理器
*/
export class MessageQueue {
private logger = createLogger('MessageQueue');
private config: MessageQueueConfig;
private stats: QueueStats;
// 队列存储
private primaryQueue: QueuedMessage[] = [];
private priorityQueues: Map<MessagePriority, QueuedMessage[]> = new Map();
private retryQueue: QueuedMessage[] = [];
private processingMap: Map<string, QueuedMessage> = new Map();
// 定时器
private processingTimer?: ITimer;
private retryTimer?: ITimer;
private cleanupTimer?: ITimer;
// 事件处理器
private eventHandlers: Partial<MessageQueueEvents> = {};
// 发送回调
private sendCallback?: (message: INetworkMessage) => Promise<boolean>;
// 性能统计
private processingTimes: number[] = [];
/**
* 构造函数
*/
constructor(config: Partial<MessageQueueConfig> = {}) {
this.config = {
maxQueueSize: 1000,
maxRetries: 3,
retryDelay: 1000,
processingInterval: 100,
enablePriority: true,
enableReliableDelivery: true,
defaultTimeout: 30000,
...config
};
this.stats = {
totalQueued: 0,
totalProcessed: 0,
totalFailed: 0,
currentSize: 0,
averageProcessingTime: 0,
messagesByPriority: {
[MessagePriority.Low]: 0,
[MessagePriority.Normal]: 0,
[MessagePriority.High]: 0,
[MessagePriority.Critical]: 0
},
reliableMessages: 0,
expiredMessages: 0
};
// 初始化优先级队列
if (this.config.enablePriority) {
this.priorityQueues.set(MessagePriority.Critical, []);
this.priorityQueues.set(MessagePriority.High, []);
this.priorityQueues.set(MessagePriority.Normal, []);
this.priorityQueues.set(MessagePriority.Low, []);
}
}
/**
* 启动队列处理
*/
start(sendCallback: (message: INetworkMessage) => Promise<boolean>): void {
this.sendCallback = sendCallback;
this.processingTimer = NetworkTimerManager.schedule(
this.config.processingInterval / 1000,
true, // 重复执行
this,
() => this.processQueue()
);
if (this.config.maxRetries > 0) {
this.retryTimer = NetworkTimerManager.schedule(
this.config.retryDelay / 1000,
true, // 重复执行
this,
() => this.processRetryQueue()
);
}
this.cleanupTimer = NetworkTimerManager.schedule(
10, // 10秒
true, // 重复执行
this,
() => this.cleanupExpiredMessages()
);
this.logger.info('消息队列已启动');
}
/**
* 停止队列处理
*/
stop(): void {
if (this.processingTimer) {
this.processingTimer.stop();
this.processingTimer = undefined;
}
if (this.retryTimer) {
this.retryTimer.stop();
this.retryTimer = undefined;
}
if (this.cleanupTimer) {
this.cleanupTimer.stop();
this.cleanupTimer = undefined;
}
this.logger.info('消息队列已停止');
}
/**
* 将消息加入队列
*/
enqueue(
message: INetworkMessage,
options: {
priority?: MessagePriority;
reliable?: boolean;
timeout?: number;
maxRetries?: number;
callback?: (success: boolean, error?: Error) => void;
} = {}
): boolean {
// 检查队列大小限制
if (this.getTotalSize() >= this.config.maxQueueSize) {
const droppedMessage = this.createQueuedMessage(message, options);
this.eventHandlers.queueFull?.(droppedMessage);
this.logger.warn('队列已满,丢弃消息:', message.type);
return false;
}
const queuedMessage = this.createQueuedMessage(message, options);
// 根据配置选择队列策略
if (this.config.enablePriority) {
this.enqueueToPriorityQueue(queuedMessage);
} else {
this.primaryQueue.push(queuedMessage);
}
this.updateQueueStats(queuedMessage);
this.eventHandlers.messageQueued?.(queuedMessage);
return true;
}
/**
* 清空队列
*/
clear(): number {
const count = this.getTotalSize();
this.primaryQueue.length = 0;
this.retryQueue.length = 0;
this.processingMap.clear();
for (const queue of this.priorityQueues.values()) {
queue.length = 0;
}
this.stats.currentSize = 0;
this.logger.info(`已清空队列,清理了 ${count} 条消息`);
return count;
}
/**
* 获取队列统计信息
*/
getStats(): QueueStats {
this.updateCurrentStats();
return { ...this.stats };
}
/**
* 重置统计信息
*/
resetStats(): void {
this.stats = {
totalQueued: 0,
totalProcessed: 0,
totalFailed: 0,
currentSize: this.getTotalSize(),
averageProcessingTime: 0,
messagesByPriority: {
[MessagePriority.Low]: 0,
[MessagePriority.Normal]: 0,
[MessagePriority.High]: 0,
[MessagePriority.Critical]: 0
},
reliableMessages: 0,
expiredMessages: 0
};
this.processingTimes.length = 0;
}
/**
* 设置事件处理器
*/
on<K extends keyof MessageQueueEvents>(event: K, handler: MessageQueueEvents[K]): void {
this.eventHandlers[event] = handler;
}
/**
* 移除事件处理器
*/
off<K extends keyof MessageQueueEvents>(event: K): void {
delete this.eventHandlers[event];
}
/**
* 更新配置
*/
updateConfig(newConfig: Partial<MessageQueueConfig>): void {
Object.assign(this.config, newConfig);
this.logger.info('消息队列配置已更新:', newConfig);
}
/**
* 获取队列中的消息数量
*/
size(): number {
return this.getTotalSize();
}
/**
* 检查队列是否为空
*/
isEmpty(): boolean {
return this.getTotalSize() === 0;
}
/**
* 检查队列是否已满
*/
isFull(): boolean {
return this.getTotalSize() >= this.config.maxQueueSize;
}
/**
* 创建队列消息
*/
private createQueuedMessage(
message: INetworkMessage,
options: any
): QueuedMessage {
const priority = options.priority || this.getMessagePriority(message);
const reliable = options.reliable ?? this.isReliableMessage(message);
return {
id: `${message.messageId}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
message,
priority,
timestamp: Date.now(),
retryCount: 0,
maxRetries: options.maxRetries ?? this.config.maxRetries,
reliable,
timeoutMs: options.timeout ?? this.config.defaultTimeout,
callback: options.callback
};
}
/**
* 将消息加入优先级队列
*/
private enqueueToPriorityQueue(message: QueuedMessage): void {
const queue = this.priorityQueues.get(message.priority);
if (queue) {
queue.push(message);
} else {
this.primaryQueue.push(message);
}
}
/**
* 从队列中取出下一条消息
*/
private dequeue(): QueuedMessage | undefined {
if (this.config.enablePriority) {
// 按优先级顺序处理
for (const priority of [MessagePriority.Critical, MessagePriority.High, MessagePriority.Normal, MessagePriority.Low]) {
const queue = this.priorityQueues.get(priority);
if (queue && queue.length > 0) {
return queue.shift();
}
}
}
return this.primaryQueue.shift();
}
/**
* 处理队列
*/
private async processQueue(): Promise<void> {
if (!this.sendCallback || this.getTotalSize() === 0) {
return;
}
const message = this.dequeue();
if (!message) {
return;
}
// 检查消息是否过期
if (this.isMessageExpired(message)) {
this.handleExpiredMessage(message);
return;
}
const startTime = Date.now();
try {
// 将消息标记为处理中
this.processingMap.set(message.id, message);
const success = await this.sendCallback(message.message);
const processingTime = Date.now() - startTime;
this.updateProcessingTime(processingTime);
if (success) {
this.handleSuccessfulMessage(message);
} else {
this.handleFailedMessage(message, new Error('发送失败'));
}
} catch (error) {
this.handleFailedMessage(message, error as Error);
} finally {
this.processingMap.delete(message.id);
}
}
/**
* 处理重试队列
*/
private async processRetryQueue(): Promise<void> {
if (this.retryQueue.length === 0 || !this.sendCallback) {
return;
}
const message = this.retryQueue.shift();
if (!message) {
return;
}
// 检查是否可以重试
if (message.retryCount >= message.maxRetries) {
this.handleFailedMessage(message, new Error('达到最大重试次数'));
return;
}
// 检查消息是否过期
if (this.isMessageExpired(message)) {
this.handleExpiredMessage(message);
return;
}
message.retryCount++;
try {
const success = await this.sendCallback(message.message);
if (success) {
this.handleSuccessfulMessage(message);
} else {
// 重新加入重试队列
this.retryQueue.push(message);
}
} catch (error) {
// 重新加入重试队列
this.retryQueue.push(message);
}
}
/**
* 处理成功的消息
*/
private handleSuccessfulMessage(message: QueuedMessage): void {
this.stats.totalProcessed++;
if (message.callback) {
try {
message.callback(true);
} catch (error) {
this.logger.error('消息回调执行失败:', error);
}
}
this.eventHandlers.messageProcessed?.(message, true);
}
/**
* 处理失败的消息
*/
private handleFailedMessage(message: QueuedMessage, error: Error): void {
// 如果是可靠消息且未达到最大重试次数,加入重试队列
if (message.reliable && message.retryCount < message.maxRetries) {
this.retryQueue.push(message);
} else {
this.stats.totalFailed++;
if (message.callback) {
try {
message.callback(false, error);
} catch (callbackError) {
this.logger.error('消息回调执行失败:', callbackError);
}
}
this.eventHandlers.messageFailed?.(message, error);
}
this.eventHandlers.messageProcessed?.(message, false);
}
/**
* 处理过期消息
*/
private handleExpiredMessage(message: QueuedMessage): void {
this.stats.expiredMessages++;
if (message.callback) {
try {
message.callback(false, new Error('消息已过期'));
} catch (error) {
this.logger.error('消息回调执行失败:', error);
}
}
this.eventHandlers.messageExpired?.(message);
}
/**
* 清理过期消息
*/
private cleanupExpiredMessages(): void {
const now = Date.now();
let cleanedCount = 0;
// 清理主队列
this.primaryQueue = this.primaryQueue.filter(msg => {
if (this.isMessageExpired(msg)) {
this.handleExpiredMessage(msg);
cleanedCount++;
return false;
}
return true;
});
// 清理优先级队列
for (const [priority, queue] of this.priorityQueues) {
this.priorityQueues.set(priority, queue.filter(msg => {
if (this.isMessageExpired(msg)) {
this.handleExpiredMessage(msg);
cleanedCount++;
return false;
}
return true;
}));
}
// 清理重试队列
this.retryQueue = this.retryQueue.filter(msg => {
if (this.isMessageExpired(msg)) {
this.handleExpiredMessage(msg);
cleanedCount++;
return false;
}
return true;
});
if (cleanedCount > 0) {
this.logger.debug(`清理了 ${cleanedCount} 条过期消息`);
}
}
/**
* 检查消息是否过期
*/
private isMessageExpired(message: QueuedMessage): boolean {
if (!message.timeoutMs) {
return false;
}
return Date.now() - message.timestamp > message.timeoutMs;
}
/**
* 获取消息的默认优先级
*/
private getMessagePriority(message: INetworkMessage): MessagePriority {
switch (message.type) {
case MessageType.HEARTBEAT:
return MessagePriority.Low;
case MessageType.CONNECT:
case MessageType.DISCONNECT:
return MessagePriority.High;
case MessageType.ERROR:
return MessagePriority.Critical;
default:
return MessagePriority.Normal;
}
}
/**
* 检查消息是否需要可靠传输
*/
private isReliableMessage(message: INetworkMessage): boolean {
if (!this.config.enableReliableDelivery) {
return false;
}
// 某些消息类型默认需要可靠传输
const reliableTypes = [
MessageType.CONNECT,
MessageType.RPC_CALL,
MessageType.ENTITY_CREATE,
MessageType.ENTITY_DESTROY
];
return reliableTypes.includes(message.type) || message.reliable === true;
}
/**
* 获取总队列大小
*/
private getTotalSize(): number {
let size = this.primaryQueue.length + this.retryQueue.length + this.processingMap.size;
for (const queue of this.priorityQueues.values()) {
size += queue.length;
}
return size;
}
/**
* 更新队列统计
*/
private updateQueueStats(message: QueuedMessage): void {
this.stats.totalQueued++;
this.stats.currentSize = this.getTotalSize();
this.stats.messagesByPriority[message.priority]++;
if (message.reliable) {
this.stats.reliableMessages++;
}
}
/**
* 更新当前统计
*/
private updateCurrentStats(): void {
this.stats.currentSize = this.getTotalSize();
}
/**
* 更新处理时间统计
*/
private updateProcessingTime(time: number): void {
this.processingTimes.push(time);
// 保持最近1000个样本
if (this.processingTimes.length > 1000) {
this.processingTimes.shift();
}
// 计算平均处理时间
this.stats.averageProcessingTime =
this.processingTimes.reduce((sum, t) => sum + t, 0) / this.processingTimes.length;
}
/**
* 获取队列详细状态
*/
getDetailedStatus() {
return {
stats: this.getStats(),
config: this.config,
queueSizes: {
primary: this.primaryQueue.length,
retry: this.retryQueue.length,
processing: this.processingMap.size,
priorities: Object.fromEntries(
Array.from(this.priorityQueues.entries()).map(([priority, queue]) => [priority, queue.length])
)
},
isRunning: !!this.processingTimer,
processingTimes: [...this.processingTimes]
};
}
}

View File

@@ -0,0 +1,698 @@
/**
* 网络客户端核心类
* 负责客户端连接管理、服务器通信和本地状态同步
*/
import { createLogger } from '@esengine/ecs-framework';
import {
IConnectionOptions,
ConnectionState,
IConnectionStats,
MessageType,
INetworkMessage,
IConnectMessage,
IConnectResponseMessage,
IHeartbeatMessage,
NetworkErrorType,
EventEmitter
} from '@esengine/network-shared';
import { WebSocketClient } from '../transport/WebSocketClient';
import { ReconnectionManager } from '../transport/ReconnectionManager';
import { JSONSerializer } from '@esengine/network-shared';
import { MessageManager } from '@esengine/network-shared';
import { ErrorHandler } from '@esengine/network-shared';
import { HeartbeatManager } from '@esengine/network-shared';
/**
* 网络客户端配置
*/
export interface NetworkClientConfig {
connection: IConnectionOptions;
features: {
enableHeartbeat: boolean;
enableReconnection: boolean;
enableCompression: boolean;
enableMessageQueue: boolean;
};
authentication: {
autoAuthenticate: boolean;
credentials?: Record<string, any>;
};
}
/**
* 客户端状态
*/
export enum ClientState {
Disconnected = 'disconnected',
Connecting = 'connecting',
Connected = 'connected',
Authenticating = 'authenticating',
Authenticated = 'authenticated',
Reconnecting = 'reconnecting',
Error = 'error'
}
/**
* 客户端统计信息
*/
export interface ClientStats {
state: ClientState;
connectionState: ConnectionState;
connectionTime?: number;
lastConnectTime?: number;
reconnectAttempts: number;
totalReconnects: number;
messages: {
sent: number;
received: number;
queued: number;
errors: number;
};
latency?: number;
uptime: number;
}
/**
* 网络客户端事件接口
*/
export interface NetworkClientEvents {
connected: () => void;
disconnected: (reason?: string) => void;
authenticated: (clientId: string) => void;
authenticationFailed: (error: string) => void;
reconnecting: (attempt: number) => void;
reconnected: () => void;
reconnectionFailed: () => void;
messageReceived: (message: INetworkMessage) => void;
error: (error: Error) => void;
stateChanged: (oldState: ClientState, newState: ClientState) => void;
}
/**
* 网络客户端核心实现
*/
export class NetworkClient extends EventEmitter {
private logger = createLogger('NetworkClient');
private config: NetworkClientConfig;
private state: ClientState = ClientState.Disconnected;
private stats: ClientStats;
private clientId?: string;
private connectTime?: number;
// 核心组件
private transport?: WebSocketClient;
private reconnectionManager: ReconnectionManager;
private serializer: JSONSerializer;
private messageManager: MessageManager;
private errorHandler: ErrorHandler;
private heartbeatManager: HeartbeatManager;
// 事件处理器
private eventHandlers: Partial<NetworkClientEvents> = {};
// 消息队列
private messageQueue: INetworkMessage[] = [];
private isProcessingQueue = false;
/**
* 构造函数
*/
constructor(config: Partial<NetworkClientConfig> = {}) {
super();
this.config = {
connection: {
timeout: 10000,
reconnectInterval: 3000,
maxReconnectAttempts: 5,
autoReconnect: true,
...config.connection
},
features: {
enableHeartbeat: true,
enableReconnection: true,
enableCompression: true,
enableMessageQueue: true,
...config.features
},
authentication: {
autoAuthenticate: true,
...config.authentication
}
};
this.stats = {
state: ClientState.Disconnected,
connectionState: ConnectionState.Disconnected,
reconnectAttempts: 0,
totalReconnects: 0,
messages: {
sent: 0,
received: 0,
queued: 0,
errors: 0
},
uptime: 0
};
// 初始化核心组件
this.reconnectionManager = new ReconnectionManager({
enabled: this.config.features.enableReconnection,
maxAttempts: this.config.connection.maxReconnectAttempts,
initialDelay: this.config.connection.reconnectInterval
});
this.serializer = new JSONSerializer({
enableTypeChecking: true,
enableCompression: this.config.features.enableCompression
});
this.messageManager = new MessageManager({
enableTimestampValidation: true,
enableMessageDeduplication: true
});
this.errorHandler = new ErrorHandler({
maxRetryAttempts: 3,
enableAutoRecovery: true
});
this.heartbeatManager = new HeartbeatManager({
interval: 30000,
timeout: 60000,
enableLatencyMeasurement: true
});
this.setupEventHandlers();
}
/**
* 连接到服务器
*/
async connect(url: string, options?: IConnectionOptions): Promise<void> {
if (this.state === ClientState.Connected || this.state === ClientState.Connecting) {
this.logger.warn('客户端已连接或正在连接');
return;
}
this.setState(ClientState.Connecting);
try {
// 合并连接选项
const connectionOptions = { ...this.config.connection, ...options };
// 创建传输层
this.transport = new WebSocketClient();
this.setupTransportEvents();
// 连接到服务器
await this.transport.connect(url, connectionOptions);
this.setState(ClientState.Connected);
this.connectTime = Date.now();
this.stats.lastConnectTime = this.connectTime;
this.logger.info(`已连接到服务器: ${url}`);
// 启动心跳
if (this.config.features.enableHeartbeat) {
this.startHeartbeat();
}
// 发送连接消息
await this.sendConnectMessage();
// 处理队列中的消息
if (this.config.features.enableMessageQueue) {
this.processMessageQueue();
}
this.eventHandlers.connected?.();
} catch (error) {
this.setState(ClientState.Error);
this.logger.error('连接失败:', error);
this.handleError(error as Error);
throw error;
}
}
/**
* 断开连接
*/
async disconnect(reason?: string): Promise<void> {
if (this.state === ClientState.Disconnected) {
return;
}
this.logger.info(`断开连接: ${reason || '用户主动断开'}`);
// 停止重连
this.reconnectionManager.stopReconnection('用户主动断开');
// 停止心跳
this.heartbeatManager.stop();
// 断开传输层连接
if (this.transport) {
await this.transport.disconnect(reason);
this.transport = undefined;
}
this.setState(ClientState.Disconnected);
this.connectTime = undefined;
this.clientId = undefined;
this.eventHandlers.disconnected?.(reason);
}
/**
* 发送消息
*/
send<T extends INetworkMessage>(message: T): boolean {
if (!this.transport || this.state !== ClientState.Connected && this.state !== ClientState.Authenticated) {
if (this.config.features.enableMessageQueue) {
this.queueMessage(message);
return true;
} else {
this.logger.warn('客户端未连接,无法发送消息');
return false;
}
}
try {
const serializedMessage = this.serializer.serialize(message);
this.transport.send(serializedMessage.data);
this.stats.messages.sent++;
return true;
} catch (error) {
this.logger.error('发送消息失败:', error);
this.stats.messages.errors++;
this.handleError(error as Error);
return false;
}
}
/**
* 获取客户端状态
*/
getState(): ClientState {
return this.state;
}
/**
* 获取连接状态
*/
getConnectionState(): ConnectionState {
return this.transport?.getConnectionState() || ConnectionState.Disconnected;
}
/**
* 获取客户端统计信息
*/
getStats(): ClientStats {
const currentStats = { ...this.stats };
currentStats.connectionState = this.getConnectionState();
currentStats.reconnectAttempts = this.reconnectionManager.getState().currentAttempt;
currentStats.totalReconnects = this.reconnectionManager.getStats().successfulReconnections;
if (this.connectTime) {
currentStats.uptime = Date.now() - this.connectTime;
}
const transportStats = this.transport?.getStats();
if (transportStats) {
currentStats.latency = transportStats.latency;
}
currentStats.messages.queued = this.messageQueue.length;
return currentStats;
}
/**
* 获取客户端ID
*/
getClientId(): string | undefined {
return this.clientId;
}
/**
* 检查是否已连接
*/
isConnected(): boolean {
return this.state === ClientState.Connected || this.state === ClientState.Authenticated;
}
/**
* 检查是否已认证
*/
isAuthenticated(): boolean {
return this.state === ClientState.Authenticated;
}
/**
* 手动触发重连
*/
reconnect(): void {
if (this.config.features.enableReconnection) {
this.reconnectionManager.forceReconnect();
}
}
/**
* 设置事件处理器
*/
override on<K extends keyof NetworkClientEvents>(event: K, handler: NetworkClientEvents[K]): this {
this.eventHandlers[event] = handler;
return this;
}
/**
* 移除事件处理器
*/
override off<K extends keyof NetworkClientEvents>(event: K): this {
delete this.eventHandlers[event];
return this;
}
/**
* 更新配置
*/
updateConfig(newConfig: Partial<NetworkClientConfig>): void {
Object.assign(this.config, newConfig);
this.logger.info('客户端配置已更新:', newConfig);
}
/**
* 销毁客户端
*/
async destroy(): Promise<void> {
await this.disconnect('客户端销毁');
// 清理组件
this.reconnectionManager.reset();
this.heartbeatManager.stop();
this.messageQueue.length = 0;
this.removeAllListeners();
}
/**
* 设置客户端状态
*/
private setState(newState: ClientState): void {
if (this.state === newState) return;
const oldState = this.state;
this.state = newState;
this.stats.state = newState;
this.logger.debug(`客户端状态变化: ${oldState} -> ${newState}`);
this.eventHandlers.stateChanged?.(oldState, newState);
}
/**
* 设置事件处理器
*/
private setupEventHandlers(): void {
// 重连管理器事件
this.reconnectionManager.on('reconnectStarted', (attempt) => {
this.setState(ClientState.Reconnecting);
this.eventHandlers.reconnecting?.(attempt);
});
this.reconnectionManager.on('reconnectSucceeded', () => {
this.setState(ClientState.Connected);
this.stats.totalReconnects++;
this.eventHandlers.reconnected?.();
});
this.reconnectionManager.on('maxAttemptsReached', () => {
this.setState(ClientState.Error);
this.eventHandlers.reconnectionFailed?.();
});
// 心跳管理器事件
this.heartbeatManager.on('healthStatusChanged', (isHealthy) => {
if (!isHealthy && this.config.features.enableReconnection) {
this.logger.warn('连接不健康,开始重连');
this.reconnectionManager.startReconnection();
}
});
// 错误处理器事件
this.errorHandler.on('criticalError', (error) => {
this.setState(ClientState.Error);
this.eventHandlers.error?.(new Error(error.message));
});
// 设置重连回调
this.reconnectionManager.setReconnectCallback(async () => {
if (this.transport) {
await this.transport.reconnect();
}
});
}
/**
* 设置传输层事件
*/
private setupTransportEvents(): void {
if (!this.transport) return;
this.transport.onMessage((data) => {
this.handleMessage(data);
});
this.transport.onConnectionStateChange((state) => {
this.handleConnectionStateChange(state);
});
this.transport.onError((error) => {
this.handleTransportError(error);
});
}
/**
* 处理接收到的消息
*/
private handleMessage(data: ArrayBuffer | string): void {
try {
const deserializationResult = this.serializer.deserialize<INetworkMessage>(data);
if (!deserializationResult.isValid) {
this.logger.warn(`消息反序列化失败: ${deserializationResult.errors?.join(', ')}`);
this.stats.messages.errors++;
return;
}
const message = deserializationResult.data;
// 验证消息
const validationResult = this.messageManager.validateMessage(message);
if (!validationResult.isValid) {
this.logger.warn(`消息验证失败: ${validationResult.errors.join(', ')}`);
this.stats.messages.errors++;
return;
}
this.stats.messages.received++;
// 处理特定类型的消息
this.processMessage(message);
this.eventHandlers.messageReceived?.(message);
} catch (error) {
this.logger.error('处理消息失败:', error);
this.stats.messages.errors++;
this.handleError(error as Error);
}
}
/**
* 处理连接状态变化
*/
private handleConnectionStateChange(state: ConnectionState): void {
this.logger.debug(`传输层连接状态: ${state}`);
switch (state) {
case ConnectionState.Connected:
this.reconnectionManager.onReconnectionSuccess();
break;
case ConnectionState.Disconnected:
case ConnectionState.Failed:
if (this.config.features.enableReconnection) {
this.reconnectionManager.startReconnection();
} else {
this.setState(ClientState.Disconnected);
}
break;
}
}
/**
* 处理传输层错误
*/
private handleTransportError(error: Error): void {
this.logger.error('传输层错误:', error);
this.handleError(error);
}
/**
* 处理错误
*/
private handleError(error: Error): void {
this.errorHandler.handleError(error, 'NetworkClient');
this.eventHandlers.error?.(error);
}
/**
* 处理具体消息类型
*/
private processMessage(message: INetworkMessage): void {
switch (message.type) {
case MessageType.CONNECT:
this.handleConnectResponse(message as IConnectResponseMessage);
break;
case MessageType.HEARTBEAT:
this.handleHeartbeatResponse(message as IHeartbeatMessage);
break;
}
}
/**
* 发送连接消息
*/
private async sendConnectMessage(): Promise<void> {
const connectMessage: IConnectMessage = this.messageManager.createMessage(
MessageType.CONNECT,
{
clientVersion: '1.0.0',
protocolVersion: '1.0.0',
authToken: this.config.authentication.credentials?.token,
clientInfo: {
name: 'ECS Network Client',
platform: typeof window !== 'undefined' ? 'browser' : 'node',
version: '1.0.0'
}
},
'client'
);
this.send(connectMessage);
}
/**
* 处理连接响应
*/
private handleConnectResponse(message: IConnectResponseMessage): void {
if (message.data.success) {
this.clientId = message.data.clientId;
if (this.config.authentication.autoAuthenticate) {
this.setState(ClientState.Authenticated);
this.eventHandlers.authenticated?.(this.clientId!);
}
this.logger.info(`连接成功客户端ID: ${this.clientId}`);
} else {
this.logger.error(`连接失败: ${message.data.error}`);
this.setState(ClientState.Error);
this.eventHandlers.authenticationFailed?.(message.data.error || '连接失败');
}
}
/**
* 处理心跳响应
*/
private handleHeartbeatResponse(message: IHeartbeatMessage): void {
this.heartbeatManager.handleHeartbeatResponse({
type: MessageType.HEARTBEAT,
clientTime: message.data.clientTime,
serverTime: message.data.serverTime
});
}
/**
* 启动心跳
*/
private startHeartbeat(): void {
this.heartbeatManager.start((heartbeatMessage) => {
const message: IHeartbeatMessage = this.messageManager.createMessage(
MessageType.HEARTBEAT,
heartbeatMessage,
this.clientId || 'client'
);
this.send(message);
});
}
/**
* 将消息加入队列
*/
private queueMessage(message: INetworkMessage): void {
this.messageQueue.push(message);
this.stats.messages.queued = this.messageQueue.length;
this.logger.debug(`消息已加入队列,当前队列长度: ${this.messageQueue.length}`);
}
/**
* 处理消息队列
*/
private async processMessageQueue(): Promise<void> {
if (this.isProcessingQueue || this.messageQueue.length === 0) {
return;
}
this.isProcessingQueue = true;
try {
while (this.messageQueue.length > 0 && this.isConnected()) {
const message = this.messageQueue.shift()!;
if (this.send(message)) {
this.logger.debug(`队列消息发送成功,剩余: ${this.messageQueue.length}`);
} else {
// 发送失败,重新加入队列头部
this.messageQueue.unshift(message);
break;
}
}
} finally {
this.isProcessingQueue = false;
this.stats.messages.queued = this.messageQueue.length;
}
}
/**
* 清空消息队列
*/
public clearMessageQueue(): number {
const count = this.messageQueue.length;
this.messageQueue.length = 0;
this.stats.messages.queued = 0;
this.logger.info(`已清空消息队列,清理了 ${count} 条消息`);
return count;
}
/**
* 获取延迟信息
*/
public getLatency(): number | undefined {
return this.heartbeatManager.getStatus().latency;
}
/**
* 获取心跳统计
*/
public getHeartbeatStats() {
return this.heartbeatManager.getStats();
}
}

View File

@@ -3,22 +3,14 @@
* ECS Framework网络层 - 客户端实现
*/
// 核心客户端 (待实现)
// export * from './core/NetworkClient';
// export * from './core/ServerConnection';
// 核心客户端
export * from './core/NetworkClient';
export * from './core/MessageQueue';
export * from './core/ConnectionStateManager';
// 传输层 (待实现)
// export * from './transport/WebSocketClient';
// export * from './transport/HttpClient';
// 系统层 (待实现)
// export * from './systems/ClientSyncSystem';
// export * from './systems/ClientRpcSystem';
// export * from './systems/InterpolationSystem';
// 平台适配器 (待实现)
// export * from './adapters/BrowserAdapter';
// export * from './adapters/CocosAdapter';
// 传输层
export * from './transport/WebSocketClient';
export * from './transport/ReconnectionManager';
// 重新导出shared包的类型
export * from '@esengine/network-shared';

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;
}
}

View File

@@ -0,0 +1,103 @@
/**
* 网络层专用Timer实现
* 实现core库的ITimer接口但使用浏览器/Node.js的原生定时器
*/
import { ITimer } from '@esengine/ecs-framework';
/**
* 网络层Timer实现
*/
export class NetworkTimer<TContext = unknown> implements ITimer<TContext> {
public context: TContext;
private timerId?: number;
private callback: (timer: ITimer<TContext>) => void;
private _isDone = false;
constructor(
timeInMilliseconds: number,
repeats: boolean,
context: TContext,
onTime: (timer: ITimer<TContext>) => void
) {
this.context = context;
this.callback = onTime;
if (repeats) {
this.timerId = window.setInterval(() => {
this.callback(this);
}, timeInMilliseconds) as any;
} else {
this.timerId = window.setTimeout(() => {
this.callback(this);
this._isDone = true;
}, timeInMilliseconds) as any;
}
}
stop(): void {
if (this.timerId !== undefined) {
clearTimeout(this.timerId);
clearInterval(this.timerId);
this.timerId = undefined;
}
this._isDone = true;
}
reset(): void {
// 对于基于setTimeout的实现reset意义不大
// 如果需要重置应该stop然后重新创建
}
getContext<T>(): T {
return this.context as unknown as T;
}
get isDone(): boolean {
return this._isDone;
}
}
/**
* 网络Timer管理器
*/
export class NetworkTimerManager {
private static timers: Set<NetworkTimer> = new Set();
/**
* 创建一个定时器
*/
static schedule<TContext = unknown>(
timeInSeconds: number,
repeats: boolean,
context: TContext,
onTime: (timer: ITimer<TContext>) => void
): ITimer<TContext> {
const timer = new NetworkTimer(
timeInSeconds * 1000, // 转为毫秒
repeats,
context,
onTime
);
this.timers.add(timer as any);
// 如果是一次性定时器,完成后自动清理
if (!repeats) {
setTimeout(() => {
this.timers.delete(timer as any);
}, timeInSeconds * 1000 + 100);
}
return timer;
}
/**
* 清理所有定时器
*/
static cleanup(): void {
for (const timer of this.timers) {
timer.stop();
}
this.timers.clear();
}
}

View File

@@ -0,0 +1,4 @@
/**
* 网络客户端工具类
*/
export * from './NetworkTimer';

View File

@@ -1,65 +0,0 @@
/**
* Jest测试环境设置 - 客户端
*/
// 导入reflect-metadata以支持装饰器
import 'reflect-metadata';
// 模拟浏览器环境的WebSocket
Object.defineProperty(global, 'WebSocket', {
value: class MockWebSocket {
static CONNECTING = 0;
static OPEN = 1;
static CLOSING = 2;
static CLOSED = 3;
readyState = MockWebSocket.CONNECTING;
url: string;
onopen: ((event: Event) => void) | null = null;
onclose: ((event: CloseEvent) => void) | null = null;
onmessage: ((event: MessageEvent) => void) | null = null;
onerror: ((event: Event) => void) | null = null;
constructor(url: string) {
this.url = url;
// 模拟异步连接
setTimeout(() => {
this.readyState = MockWebSocket.OPEN;
if (this.onopen) {
this.onopen(new Event('open'));
}
}, 0);
}
send(data: string | ArrayBuffer) {
// 模拟发送
}
close() {
this.readyState = MockWebSocket.CLOSED;
if (this.onclose) {
this.onclose(new CloseEvent('close'));
}
}
}
});
// 全局测试配置
beforeAll(() => {
// 设置测试环境
process.env.NODE_ENV = 'test';
process.env.NETWORK_ENV = 'client';
});
afterAll(() => {
// 清理测试环境
});
beforeEach(() => {
// 每个测试前的准备工作
});
afterEach(() => {
// 每个测试后的清理工作
// 清理可能的网络连接、定时器等
});