传输层实现(客户端/服务端,链接管理和心跳机制,重连机制)
消息序列化(json序列化,消息压缩,消息ID和时间戳) 网络服务器核心(networkserver/基础room/链接状态同步) 网络客户端核心(networkclient/消息队列)
This commit is contained in:
@@ -9,18 +9,21 @@ export * from './types/TransportTypes';
|
||||
|
||||
// 协议消息
|
||||
export * from './protocols/MessageTypes';
|
||||
export * from './protocols/MessageManager';
|
||||
|
||||
// 核心组件
|
||||
export * from './components/NetworkIdentity';
|
||||
|
||||
// 装饰器系统 (待实现)
|
||||
// export * from './decorators/SyncVar';
|
||||
// export * from './decorators/ServerRpc';
|
||||
// export * from './decorators/ClientRpc';
|
||||
// export * from './decorators/NetworkComponent';
|
||||
// 传输层
|
||||
export * from './transport/HeartbeatManager';
|
||||
export * from './transport/ErrorHandler';
|
||||
|
||||
// 事件系统
|
||||
export * from './events/NetworkEvents';
|
||||
|
||||
// 序列化系统 (待实现)
|
||||
// export * from './serialization/NetworkSerializer';
|
||||
// 序列化系统
|
||||
export * from './serialization/JSONSerializer';
|
||||
export * from './serialization/MessageCompressor';
|
||||
|
||||
// 工具类
|
||||
export * from './utils';
|
||||
502
packages/network-shared/src/protocols/MessageManager.ts
Normal file
502
packages/network-shared/src/protocols/MessageManager.ts
Normal file
@@ -0,0 +1,502 @@
|
||||
/**
|
||||
* 消息管理器
|
||||
* 负责消息ID生成、时间戳管理和消息验证
|
||||
*/
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { INetworkMessage, MessageType } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* 消息ID生成器类型
|
||||
*/
|
||||
export enum MessageIdGeneratorType {
|
||||
UUID = 'uuid',
|
||||
SNOWFLAKE = 'snowflake',
|
||||
SEQUENTIAL = 'sequential',
|
||||
TIMESTAMP = 'timestamp'
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息管理器配置
|
||||
*/
|
||||
export interface MessageManagerConfig {
|
||||
idGenerator: MessageIdGeneratorType;
|
||||
enableTimestampValidation: boolean;
|
||||
maxTimestampDrift: number; // 最大时间戳偏移(毫秒)
|
||||
enableMessageDeduplication: boolean;
|
||||
deduplicationWindowMs: number; // 去重窗口时间
|
||||
enableMessageOrdering: boolean;
|
||||
orderingWindowMs: number; // 排序窗口时间
|
||||
maxPendingMessages: number; // 最大待处理消息数
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息验证结果
|
||||
*/
|
||||
export interface MessageValidationResult {
|
||||
isValid: boolean;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息统计信息
|
||||
*/
|
||||
export interface MessageStats {
|
||||
totalGenerated: number;
|
||||
totalValidated: number;
|
||||
validMessages: number;
|
||||
invalidMessages: number;
|
||||
duplicateMessages: number;
|
||||
outOfOrderMessages: number;
|
||||
timestampErrors: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Snowflake ID生成器
|
||||
*/
|
||||
class SnowflakeIdGenerator {
|
||||
private static readonly EPOCH = 1640995200000; // 2022-01-01 00:00:00 UTC
|
||||
private static readonly WORKER_ID_BITS = 5;
|
||||
private static readonly DATACENTER_ID_BITS = 5;
|
||||
private static readonly SEQUENCE_BITS = 12;
|
||||
|
||||
private readonly workerId: number;
|
||||
private readonly datacenterId: number;
|
||||
private sequence = 0;
|
||||
private lastTimestamp = -1;
|
||||
|
||||
constructor(workerId: number = 1, datacenterId: number = 1) {
|
||||
this.workerId = workerId & ((1 << SnowflakeIdGenerator.WORKER_ID_BITS) - 1);
|
||||
this.datacenterId = datacenterId & ((1 << SnowflakeIdGenerator.DATACENTER_ID_BITS) - 1);
|
||||
}
|
||||
|
||||
generate(): string {
|
||||
let timestamp = Date.now();
|
||||
|
||||
if (timestamp < this.lastTimestamp) {
|
||||
throw new Error('时钟回拨,无法生成ID');
|
||||
}
|
||||
|
||||
if (timestamp === this.lastTimestamp) {
|
||||
this.sequence = (this.sequence + 1) & ((1 << SnowflakeIdGenerator.SEQUENCE_BITS) - 1);
|
||||
if (this.sequence === 0) {
|
||||
// 等待下一毫秒
|
||||
while (timestamp <= this.lastTimestamp) {
|
||||
timestamp = Date.now();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.sequence = 0;
|
||||
}
|
||||
|
||||
this.lastTimestamp = timestamp;
|
||||
|
||||
const id = ((timestamp - SnowflakeIdGenerator.EPOCH) << 22) |
|
||||
(this.datacenterId << 17) |
|
||||
(this.workerId << 12) |
|
||||
this.sequence;
|
||||
|
||||
return id.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息管理器
|
||||
*/
|
||||
export class MessageManager {
|
||||
private logger = createLogger('MessageManager');
|
||||
private config: MessageManagerConfig;
|
||||
private stats: MessageStats;
|
||||
|
||||
// ID生成器
|
||||
private sequentialId = 0;
|
||||
private snowflakeGenerator: SnowflakeIdGenerator;
|
||||
|
||||
// 消息去重和排序
|
||||
private recentMessageIds: Set<string> = new Set();
|
||||
private pendingMessages: Map<string, INetworkMessage> = new Map();
|
||||
private messageSequence: Map<string, number> = new Map();
|
||||
|
||||
// 清理定时器
|
||||
private cleanupTimer?: NodeJS.Timeout;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
constructor(config: Partial<MessageManagerConfig> = {}) {
|
||||
this.config = {
|
||||
idGenerator: MessageIdGeneratorType.UUID,
|
||||
enableTimestampValidation: true,
|
||||
maxTimestampDrift: 60000, // 1分钟
|
||||
enableMessageDeduplication: true,
|
||||
deduplicationWindowMs: 300000, // 5分钟
|
||||
enableMessageOrdering: false,
|
||||
orderingWindowMs: 10000, // 10秒
|
||||
maxPendingMessages: 1000,
|
||||
...config
|
||||
};
|
||||
|
||||
this.stats = {
|
||||
totalGenerated: 0,
|
||||
totalValidated: 0,
|
||||
validMessages: 0,
|
||||
invalidMessages: 0,
|
||||
duplicateMessages: 0,
|
||||
outOfOrderMessages: 0,
|
||||
timestampErrors: 0
|
||||
};
|
||||
|
||||
this.snowflakeGenerator = new SnowflakeIdGenerator();
|
||||
this.startCleanupTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成消息ID
|
||||
*/
|
||||
generateMessageId(): string {
|
||||
this.stats.totalGenerated++;
|
||||
|
||||
switch (this.config.idGenerator) {
|
||||
case MessageIdGeneratorType.UUID:
|
||||
return this.generateUUID();
|
||||
case MessageIdGeneratorType.SNOWFLAKE:
|
||||
return this.snowflakeGenerator.generate();
|
||||
case MessageIdGeneratorType.SEQUENTIAL:
|
||||
return (++this.sequentialId).toString();
|
||||
case MessageIdGeneratorType.TIMESTAMP:
|
||||
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
default:
|
||||
return this.generateUUID();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建网络消息
|
||||
*/
|
||||
createMessage<T extends INetworkMessage>(
|
||||
type: MessageType,
|
||||
data: any,
|
||||
senderId: string,
|
||||
options: {
|
||||
reliable?: boolean;
|
||||
priority?: number;
|
||||
timestamp?: number;
|
||||
} = {}
|
||||
): T {
|
||||
const message: INetworkMessage = {
|
||||
type,
|
||||
messageId: this.generateMessageId(),
|
||||
timestamp: options.timestamp || Date.now(),
|
||||
senderId,
|
||||
data,
|
||||
reliable: options.reliable,
|
||||
priority: options.priority
|
||||
};
|
||||
|
||||
return message as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证消息
|
||||
*/
|
||||
validateMessage(message: INetworkMessage, senderId?: string): MessageValidationResult {
|
||||
this.stats.totalValidated++;
|
||||
|
||||
const errors: string[] = [];
|
||||
const warnings: string[] = [];
|
||||
|
||||
// 基础字段验证
|
||||
if (!message.messageId) {
|
||||
errors.push('消息ID不能为空');
|
||||
}
|
||||
|
||||
if (!message.type) {
|
||||
errors.push('消息类型不能为空');
|
||||
} else if (!Object.values(MessageType).includes(message.type)) {
|
||||
errors.push(`无效的消息类型: ${message.type}`);
|
||||
}
|
||||
|
||||
if (!message.timestamp) {
|
||||
errors.push('时间戳不能为空');
|
||||
}
|
||||
|
||||
if (!message.senderId) {
|
||||
errors.push('发送者ID不能为空');
|
||||
}
|
||||
|
||||
// 发送者验证
|
||||
if (senderId && message.senderId !== senderId) {
|
||||
errors.push('消息发送者ID不匹配');
|
||||
}
|
||||
|
||||
// 时间戳验证
|
||||
if (this.config.enableTimestampValidation && message.timestamp) {
|
||||
const now = Date.now();
|
||||
const drift = Math.abs(now - message.timestamp);
|
||||
|
||||
if (drift > this.config.maxTimestampDrift) {
|
||||
errors.push(`时间戳偏移过大: ${drift}ms > ${this.config.maxTimestampDrift}ms`);
|
||||
this.stats.timestampErrors++;
|
||||
}
|
||||
|
||||
if (message.timestamp > now + 10000) { // 未来10秒以上
|
||||
warnings.push('消息时间戳来自未来');
|
||||
}
|
||||
}
|
||||
|
||||
// 消息去重验证
|
||||
if (this.config.enableMessageDeduplication) {
|
||||
if (this.recentMessageIds.has(message.messageId)) {
|
||||
errors.push('重复的消息ID');
|
||||
this.stats.duplicateMessages++;
|
||||
} else {
|
||||
this.recentMessageIds.add(message.messageId);
|
||||
}
|
||||
}
|
||||
|
||||
const isValid = errors.length === 0;
|
||||
|
||||
if (isValid) {
|
||||
this.stats.validMessages++;
|
||||
} else {
|
||||
this.stats.invalidMessages++;
|
||||
}
|
||||
|
||||
return {
|
||||
isValid,
|
||||
errors,
|
||||
warnings
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理消息排序
|
||||
*/
|
||||
processMessageOrdering(message: INetworkMessage): INetworkMessage[] {
|
||||
if (!this.config.enableMessageOrdering) {
|
||||
return [message];
|
||||
}
|
||||
|
||||
const senderId = message.senderId;
|
||||
const currentSequence = this.messageSequence.get(senderId) || 0;
|
||||
|
||||
// 检查消息是否按顺序到达
|
||||
const messageTimestamp = message.timestamp;
|
||||
const expectedSequence = currentSequence + 1;
|
||||
|
||||
// 简单的时间戳排序逻辑
|
||||
if (messageTimestamp >= expectedSequence) {
|
||||
// 消息按顺序到达
|
||||
this.messageSequence.set(senderId, messageTimestamp);
|
||||
return this.flushPendingMessages(senderId).concat([message]);
|
||||
} else {
|
||||
// 消息乱序,暂存
|
||||
this.pendingMessages.set(message.messageId, message);
|
||||
this.stats.outOfOrderMessages++;
|
||||
|
||||
// 检查是否超出最大待处理数量
|
||||
if (this.pendingMessages.size > this.config.maxPendingMessages) {
|
||||
this.logger.warn('待处理消息数量过多,清理旧消息');
|
||||
this.cleanupOldPendingMessages();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
getStats(): MessageStats {
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
resetStats(): void {
|
||||
this.stats = {
|
||||
totalGenerated: 0,
|
||||
totalValidated: 0,
|
||||
validMessages: 0,
|
||||
invalidMessages: 0,
|
||||
duplicateMessages: 0,
|
||||
outOfOrderMessages: 0,
|
||||
timestampErrors: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
updateConfig(newConfig: Partial<MessageManagerConfig>): void {
|
||||
const oldConfig = { ...this.config };
|
||||
Object.assign(this.config, newConfig);
|
||||
|
||||
// 如果去重配置改变,清理相关数据
|
||||
if (!this.config.enableMessageDeduplication && oldConfig.enableMessageDeduplication) {
|
||||
this.recentMessageIds.clear();
|
||||
}
|
||||
|
||||
// 如果排序配置改变,清理相关数据
|
||||
if (!this.config.enableMessageOrdering && oldConfig.enableMessageOrdering) {
|
||||
this.pendingMessages.clear();
|
||||
this.messageSequence.clear();
|
||||
}
|
||||
|
||||
this.logger.info('消息管理器配置已更新:', newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁管理器
|
||||
*/
|
||||
destroy(): void {
|
||||
if (this.cleanupTimer) {
|
||||
clearInterval(this.cleanupTimer);
|
||||
this.cleanupTimer = undefined;
|
||||
}
|
||||
|
||||
this.recentMessageIds.clear();
|
||||
this.pendingMessages.clear();
|
||||
this.messageSequence.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成UUID
|
||||
*/
|
||||
private generateUUID(): string {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
const r = Math.random() * 16 | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新待处理消息
|
||||
*/
|
||||
private flushPendingMessages(senderId: string): INetworkMessage[] {
|
||||
const flushedMessages: INetworkMessage[] = [];
|
||||
const messagesToRemove: string[] = [];
|
||||
|
||||
for (const [messageId, message] of this.pendingMessages) {
|
||||
if (message.senderId === senderId) {
|
||||
flushedMessages.push(message);
|
||||
messagesToRemove.push(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
// 移除已处理的消息
|
||||
messagesToRemove.forEach(id => this.pendingMessages.delete(id));
|
||||
|
||||
// 按时间戳排序
|
||||
flushedMessages.sort((a, b) => a.timestamp - b.timestamp);
|
||||
|
||||
return flushedMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期的待处理消息
|
||||
*/
|
||||
private cleanupOldPendingMessages(): void {
|
||||
const now = Date.now();
|
||||
const messagesToRemove: string[] = [];
|
||||
|
||||
for (const [messageId, message] of this.pendingMessages) {
|
||||
if (now - message.timestamp > this.config.orderingWindowMs) {
|
||||
messagesToRemove.push(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
messagesToRemove.forEach(id => this.pendingMessages.delete(id));
|
||||
|
||||
if (messagesToRemove.length > 0) {
|
||||
this.logger.debug(`清理了 ${messagesToRemove.length} 个过期的待处理消息`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动清理定时器
|
||||
*/
|
||||
private startCleanupTimer(): void {
|
||||
this.cleanupTimer = setInterval(() => {
|
||||
this.performCleanup();
|
||||
}, 60000); // 每分钟清理一次
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行清理操作
|
||||
*/
|
||||
private performCleanup(): void {
|
||||
const now = Date.now();
|
||||
|
||||
// 清理过期的消息ID(用于去重)
|
||||
if (this.config.enableMessageDeduplication) {
|
||||
// 由于Set没有时间戳,我们定期清理所有ID
|
||||
// 这是一个简化实现,实际项目中可以使用更复杂的数据结构
|
||||
if (this.recentMessageIds.size > 10000) {
|
||||
this.recentMessageIds.clear();
|
||||
this.logger.debug('清理了过期的消息ID缓存');
|
||||
}
|
||||
}
|
||||
|
||||
// 清理过期的待处理消息
|
||||
if (this.config.enableMessageOrdering) {
|
||||
this.cleanupOldPendingMessages();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息处理报告
|
||||
*/
|
||||
getProcessingReport() {
|
||||
const totalProcessed = this.stats.validMessages + this.stats.invalidMessages;
|
||||
const validRate = totalProcessed > 0 ? (this.stats.validMessages / totalProcessed) * 100 : 0;
|
||||
const duplicateRate = totalProcessed > 0 ? (this.stats.duplicateMessages / totalProcessed) * 100 : 0;
|
||||
|
||||
return {
|
||||
stats: this.getStats(),
|
||||
validationRate: validRate,
|
||||
duplicateRate: duplicateRate,
|
||||
pendingMessagesCount: this.pendingMessages.size,
|
||||
cachedMessageIdsCount: this.recentMessageIds.size,
|
||||
recommendation: this.generateRecommendation(validRate, duplicateRate)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成优化建议
|
||||
*/
|
||||
private generateRecommendation(validRate: number, duplicateRate: number): string {
|
||||
if (validRate < 90) {
|
||||
return '消息验证失败率较高,建议检查消息格式和发送逻辑';
|
||||
} else if (duplicateRate > 5) {
|
||||
return '重复消息较多,建议检查客户端重发逻辑或调整去重窗口';
|
||||
} else if (this.pendingMessages.size > this.config.maxPendingMessages * 0.8) {
|
||||
return '待处理消息过多,建议优化网络或调整排序窗口';
|
||||
} else {
|
||||
return '消息处理正常';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量验证消息
|
||||
*/
|
||||
validateMessageBatch(messages: INetworkMessage[], senderId?: string): MessageValidationResult[] {
|
||||
return messages.map(message => this.validateMessage(message, senderId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息年龄(毫秒)
|
||||
*/
|
||||
getMessageAge(message: INetworkMessage): number {
|
||||
return Date.now() - message.timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查消息是否过期
|
||||
*/
|
||||
isMessageExpired(message: INetworkMessage, maxAge: number = 300000): boolean {
|
||||
return this.getMessageAge(message) > maxAge;
|
||||
}
|
||||
}
|
||||
550
packages/network-shared/src/serialization/JSONSerializer.ts
Normal file
550
packages/network-shared/src/serialization/JSONSerializer.ts
Normal file
@@ -0,0 +1,550 @@
|
||||
/**
|
||||
* JSON序列化器
|
||||
* 提供高性能的消息序列化和反序列化功能,包括类型安全检查
|
||||
*/
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { INetworkMessage, MessageType } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* 序列化器配置
|
||||
*/
|
||||
export interface SerializerConfig {
|
||||
enableTypeChecking: boolean;
|
||||
enableCompression: boolean;
|
||||
maxMessageSize: number;
|
||||
enableProfiling: boolean;
|
||||
customSerializers?: Map<string, ICustomSerializer>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义序列化器接口
|
||||
*/
|
||||
export interface ICustomSerializer {
|
||||
serialize(data: any): any;
|
||||
deserialize(data: any): any;
|
||||
canHandle(data: any): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化结果
|
||||
*/
|
||||
export interface SerializationResult {
|
||||
data: string | Buffer;
|
||||
size: number;
|
||||
compressionRatio?: number;
|
||||
serializationTime: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化结果
|
||||
*/
|
||||
export interface DeserializationResult<T = any> {
|
||||
data: T;
|
||||
deserializationTime: number;
|
||||
isValid: boolean;
|
||||
errors?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化统计信息
|
||||
*/
|
||||
export interface SerializationStats {
|
||||
totalSerialized: number;
|
||||
totalDeserialized: number;
|
||||
totalBytes: number;
|
||||
averageSerializationTime: number;
|
||||
averageDeserializationTime: number;
|
||||
averageMessageSize: number;
|
||||
errorCount: number;
|
||||
compressionSavings: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON序列化器
|
||||
*/
|
||||
export class JSONSerializer {
|
||||
private logger = createLogger('JSONSerializer');
|
||||
private config: SerializerConfig;
|
||||
private stats: SerializationStats;
|
||||
|
||||
// 性能分析
|
||||
private serializationTimes: number[] = [];
|
||||
private deserializationTimes: number[] = [];
|
||||
private messageSizes: number[] = [];
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
constructor(config: Partial<SerializerConfig> = {}) {
|
||||
this.config = {
|
||||
enableTypeChecking: true,
|
||||
enableCompression: false,
|
||||
maxMessageSize: 1024 * 1024, // 1MB
|
||||
enableProfiling: false,
|
||||
...config
|
||||
};
|
||||
|
||||
this.stats = {
|
||||
totalSerialized: 0,
|
||||
totalDeserialized: 0,
|
||||
totalBytes: 0,
|
||||
averageSerializationTime: 0,
|
||||
averageDeserializationTime: 0,
|
||||
averageMessageSize: 0,
|
||||
errorCount: 0,
|
||||
compressionSavings: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化消息
|
||||
*/
|
||||
serialize<T extends INetworkMessage>(message: T): SerializationResult {
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
// 类型检查
|
||||
if (this.config.enableTypeChecking) {
|
||||
this.validateMessage(message);
|
||||
}
|
||||
|
||||
// 预处理消息
|
||||
const processedMessage = this.preprocessMessage(message);
|
||||
|
||||
// 序列化
|
||||
let serializedData: string;
|
||||
|
||||
// 使用自定义序列化器
|
||||
const customSerializer = this.findCustomSerializer(processedMessage);
|
||||
if (customSerializer) {
|
||||
serializedData = JSON.stringify(customSerializer.serialize(processedMessage));
|
||||
} else {
|
||||
serializedData = JSON.stringify(processedMessage, this.createReplacer());
|
||||
}
|
||||
|
||||
// 检查大小限制
|
||||
if (serializedData.length > this.config.maxMessageSize) {
|
||||
throw new Error(`消息大小超过限制: ${serializedData.length} > ${this.config.maxMessageSize}`);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const serializationTime = endTime - startTime;
|
||||
|
||||
// 更新统计
|
||||
this.updateSerializationStats(serializedData.length, serializationTime);
|
||||
|
||||
return {
|
||||
data: serializedData,
|
||||
size: serializedData.length,
|
||||
serializationTime
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
this.stats.errorCount++;
|
||||
this.logger.error('序列化失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化消息
|
||||
*/
|
||||
deserialize<T extends INetworkMessage>(data: string | ArrayBuffer): DeserializationResult<T> {
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
// 转换数据格式
|
||||
const jsonString = data instanceof ArrayBuffer ? new TextDecoder().decode(data) :
|
||||
typeof data === 'string' ? data : String(data);
|
||||
|
||||
// 解析JSON
|
||||
const parsedData = JSON.parse(jsonString, this.createReviver());
|
||||
|
||||
// 类型检查
|
||||
const validationResult = this.config.enableTypeChecking ?
|
||||
this.validateParsedMessage(parsedData) : { isValid: true, errors: [] };
|
||||
|
||||
// 后处理消息
|
||||
const processedMessage = this.postprocessMessage(parsedData);
|
||||
|
||||
const endTime = performance.now();
|
||||
const deserializationTime = endTime - startTime;
|
||||
|
||||
// 更新统计
|
||||
this.updateDeserializationStats(deserializationTime);
|
||||
|
||||
return {
|
||||
data: processedMessage as T,
|
||||
deserializationTime,
|
||||
isValid: validationResult.isValid,
|
||||
errors: validationResult.errors
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
this.stats.errorCount++;
|
||||
this.logger.error('反序列化失败:', error);
|
||||
|
||||
return {
|
||||
data: {} as T,
|
||||
deserializationTime: performance.now() - startTime,
|
||||
isValid: false,
|
||||
errors: [error instanceof Error ? error.message : '未知错误']
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量序列化
|
||||
*/
|
||||
serializeBatch<T extends INetworkMessage>(messages: T[]): SerializationResult {
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
const batchData = {
|
||||
type: 'batch',
|
||||
messages: messages.map(msg => {
|
||||
if (this.config.enableTypeChecking) {
|
||||
this.validateMessage(msg);
|
||||
}
|
||||
return this.preprocessMessage(msg);
|
||||
}),
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
const serializedData = JSON.stringify(batchData, this.createReplacer());
|
||||
|
||||
if (serializedData.length > this.config.maxMessageSize) {
|
||||
throw new Error(`批量消息大小超过限制: ${serializedData.length} > ${this.config.maxMessageSize}`);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const serializationTime = endTime - startTime;
|
||||
|
||||
this.updateSerializationStats(serializedData.length, serializationTime);
|
||||
|
||||
return {
|
||||
data: serializedData,
|
||||
size: serializedData.length,
|
||||
serializationTime
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
this.stats.errorCount++;
|
||||
this.logger.error('批量序列化失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量反序列化
|
||||
*/
|
||||
deserializeBatch<T extends INetworkMessage>(data: string | ArrayBuffer): DeserializationResult<T[]> {
|
||||
const result = this.deserialize<any>(data);
|
||||
|
||||
if (!result.isValid || !result.data.messages) {
|
||||
return {
|
||||
data: [],
|
||||
deserializationTime: result.deserializationTime,
|
||||
isValid: false,
|
||||
errors: ['无效的批量消息格式']
|
||||
};
|
||||
}
|
||||
|
||||
const messages = result.data.messages.map((msg: any) => this.postprocessMessage(msg));
|
||||
|
||||
return {
|
||||
data: messages as T[],
|
||||
deserializationTime: result.deserializationTime,
|
||||
isValid: true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
getStats(): SerializationStats {
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
resetStats(): void {
|
||||
this.stats = {
|
||||
totalSerialized: 0,
|
||||
totalDeserialized: 0,
|
||||
totalBytes: 0,
|
||||
averageSerializationTime: 0,
|
||||
averageDeserializationTime: 0,
|
||||
averageMessageSize: 0,
|
||||
errorCount: 0,
|
||||
compressionSavings: 0
|
||||
};
|
||||
|
||||
this.serializationTimes.length = 0;
|
||||
this.deserializationTimes.length = 0;
|
||||
this.messageSizes.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加自定义序列化器
|
||||
*/
|
||||
addCustomSerializer(name: string, serializer: ICustomSerializer): void {
|
||||
if (!this.config.customSerializers) {
|
||||
this.config.customSerializers = new Map();
|
||||
}
|
||||
this.config.customSerializers.set(name, serializer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除自定义序列化器
|
||||
*/
|
||||
removeCustomSerializer(name: string): boolean {
|
||||
return this.config.customSerializers?.delete(name) || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
updateConfig(newConfig: Partial<SerializerConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
this.logger.info('序列化器配置已更新:', newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证消息格式
|
||||
*/
|
||||
private validateMessage(message: INetworkMessage): void {
|
||||
if (!message.type || !message.messageId || !message.timestamp) {
|
||||
throw new Error('消息格式无效:缺少必需字段');
|
||||
}
|
||||
|
||||
if (!Object.values(MessageType).includes(message.type)) {
|
||||
throw new Error(`无效的消息类型: ${message.type}`);
|
||||
}
|
||||
|
||||
if (typeof message.timestamp !== 'number' || message.timestamp <= 0) {
|
||||
throw new Error('无效的时间戳');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证解析后的消息
|
||||
*/
|
||||
private validateParsedMessage(data: any): { isValid: boolean; errors: string[] } {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!data || typeof data !== 'object') {
|
||||
errors.push('消息必须是对象');
|
||||
} else {
|
||||
if (!data.type) errors.push('缺少消息类型');
|
||||
if (!data.messageId) errors.push('缺少消息ID');
|
||||
if (!data.timestamp) errors.push('缺少时间戳');
|
||||
if (!data.senderId) errors.push('缺少发送者ID');
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: errors.length === 0,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 预处理消息(序列化前)
|
||||
*/
|
||||
private preprocessMessage(message: INetworkMessage): any {
|
||||
// 克隆消息以避免修改原始对象
|
||||
const processed = { ...message };
|
||||
|
||||
// 处理特殊数据类型
|
||||
if (processed.data) {
|
||||
processed.data = this.serializeSpecialTypes(processed.data);
|
||||
}
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 后处理消息(反序列化后)
|
||||
*/
|
||||
private postprocessMessage(data: any): any {
|
||||
if (data.data) {
|
||||
data.data = this.deserializeSpecialTypes(data.data);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化特殊类型
|
||||
*/
|
||||
private serializeSpecialTypes(data: any): any {
|
||||
if (data instanceof Date) {
|
||||
return { __type: 'Date', value: data.toISOString() };
|
||||
} else if (data instanceof Map) {
|
||||
return { __type: 'Map', value: Array.from(data.entries()) };
|
||||
} else if (data instanceof Set) {
|
||||
return { __type: 'Set', value: Array.from(data) };
|
||||
} else if (ArrayBuffer.isView(data)) {
|
||||
return { __type: 'TypedArray', value: Array.from(data as any), constructor: data.constructor.name };
|
||||
} else if (data && typeof data === 'object') {
|
||||
const result: any = {};
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
result[key] = this.serializeSpecialTypes(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 反序列化特殊类型
|
||||
*/
|
||||
private deserializeSpecialTypes(data: any): any {
|
||||
if (data && typeof data === 'object' && data.__type) {
|
||||
switch (data.__type) {
|
||||
case 'Date':
|
||||
return new Date(data.value);
|
||||
case 'Map':
|
||||
return new Map(data.value);
|
||||
case 'Set':
|
||||
return new Set(data.value);
|
||||
case 'TypedArray':
|
||||
const constructor = (globalThis as any)[data.constructor];
|
||||
return constructor ? new constructor(data.value) : data.value;
|
||||
}
|
||||
} else if (data && typeof data === 'object') {
|
||||
const result: any = {};
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
result[key] = this.deserializeSpecialTypes(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建JSON.stringify替换函数
|
||||
*/
|
||||
private createReplacer() {
|
||||
return (key: string, value: any) => {
|
||||
// 处理循环引用
|
||||
if (value && typeof value === 'object') {
|
||||
if (value.__serializing) {
|
||||
return '[Circular Reference]';
|
||||
}
|
||||
value.__serializing = true;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建JSON.parse恢复函数
|
||||
*/
|
||||
private createReviver() {
|
||||
return (key: string, value: any) => {
|
||||
// 清理序列化标记
|
||||
if (value && typeof value === 'object') {
|
||||
delete value.__serializing;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找自定义序列化器
|
||||
*/
|
||||
private findCustomSerializer(data: any): ICustomSerializer | undefined {
|
||||
if (!this.config.customSerializers) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const serializer of this.config.customSerializers.values()) {
|
||||
if (serializer.canHandle(data)) {
|
||||
return serializer;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新序列化统计
|
||||
*/
|
||||
private updateSerializationStats(size: number, time: number): void {
|
||||
this.stats.totalSerialized++;
|
||||
this.stats.totalBytes += size;
|
||||
|
||||
this.serializationTimes.push(time);
|
||||
this.messageSizes.push(size);
|
||||
|
||||
// 保持最近1000个样本
|
||||
if (this.serializationTimes.length > 1000) {
|
||||
this.serializationTimes.shift();
|
||||
}
|
||||
if (this.messageSizes.length > 1000) {
|
||||
this.messageSizes.shift();
|
||||
}
|
||||
|
||||
// 计算平均值
|
||||
this.stats.averageSerializationTime =
|
||||
this.serializationTimes.reduce((sum, t) => sum + t, 0) / this.serializationTimes.length;
|
||||
this.stats.averageMessageSize =
|
||||
this.messageSizes.reduce((sum, s) => sum + s, 0) / this.messageSizes.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新反序列化统计
|
||||
*/
|
||||
private updateDeserializationStats(time: number): void {
|
||||
this.stats.totalDeserialized++;
|
||||
|
||||
this.deserializationTimes.push(time);
|
||||
|
||||
// 保持最近1000个样本
|
||||
if (this.deserializationTimes.length > 1000) {
|
||||
this.deserializationTimes.shift();
|
||||
}
|
||||
|
||||
// 计算平均值
|
||||
this.stats.averageDeserializationTime =
|
||||
this.deserializationTimes.reduce((sum, t) => sum + t, 0) / this.deserializationTimes.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取性能分析报告
|
||||
*/
|
||||
getPerformanceReport() {
|
||||
return {
|
||||
stats: this.getStats(),
|
||||
serializationTimes: [...this.serializationTimes],
|
||||
deserializationTimes: [...this.deserializationTimes],
|
||||
messageSizes: [...this.messageSizes],
|
||||
percentiles: {
|
||||
serialization: this.calculatePercentiles(this.serializationTimes),
|
||||
deserialization: this.calculatePercentiles(this.deserializationTimes),
|
||||
messageSize: this.calculatePercentiles(this.messageSizes)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算百分位数
|
||||
*/
|
||||
private calculatePercentiles(values: number[]) {
|
||||
if (values.length === 0) return {};
|
||||
|
||||
const sorted = [...values].sort((a, b) => a - b);
|
||||
const n = sorted.length;
|
||||
|
||||
return {
|
||||
p50: sorted[Math.floor(n * 0.5)],
|
||||
p90: sorted[Math.floor(n * 0.9)],
|
||||
p95: sorted[Math.floor(n * 0.95)],
|
||||
p99: sorted[Math.floor(n * 0.99)]
|
||||
};
|
||||
}
|
||||
}
|
||||
498
packages/network-shared/src/serialization/MessageCompressor.ts
Normal file
498
packages/network-shared/src/serialization/MessageCompressor.ts
Normal file
@@ -0,0 +1,498 @@
|
||||
/**
|
||||
* 消息压缩器
|
||||
* 提供多种压缩算法选择和压缩率统计
|
||||
*/
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import * as zlib from 'zlib';
|
||||
import { promisify } from 'util';
|
||||
|
||||
/**
|
||||
* 压缩算法类型
|
||||
*/
|
||||
export enum CompressionAlgorithm {
|
||||
NONE = 'none',
|
||||
GZIP = 'gzip',
|
||||
DEFLATE = 'deflate',
|
||||
BROTLI = 'brotli'
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩配置
|
||||
*/
|
||||
export interface CompressionConfig {
|
||||
algorithm: CompressionAlgorithm;
|
||||
level: number; // 压缩级别 (0-9)
|
||||
threshold: number; // 最小压缩阈值(字节)
|
||||
enableAsync: boolean; // 是否启用异步压缩
|
||||
chunkSize: number; // 分块大小
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩结果
|
||||
*/
|
||||
export interface CompressionResult {
|
||||
data: Buffer;
|
||||
originalSize: number;
|
||||
compressedSize: number;
|
||||
compressionRatio: number;
|
||||
compressionTime: number;
|
||||
algorithm: CompressionAlgorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩统计信息
|
||||
*/
|
||||
export interface CompressionStats {
|
||||
totalCompressed: number;
|
||||
totalDecompressed: number;
|
||||
totalOriginalBytes: number;
|
||||
totalCompressedBytes: number;
|
||||
averageCompressionRatio: number;
|
||||
averageCompressionTime: number;
|
||||
averageDecompressionTime: number;
|
||||
algorithmUsage: Record<CompressionAlgorithm, number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息压缩器
|
||||
*/
|
||||
export class MessageCompressor {
|
||||
private logger = createLogger('MessageCompressor');
|
||||
private config: CompressionConfig;
|
||||
private stats: CompressionStats;
|
||||
|
||||
// 异步压缩函数
|
||||
private gzipAsync = promisify(zlib.gzip);
|
||||
private gunzipAsync = promisify(zlib.gunzip);
|
||||
private deflateAsync = promisify(zlib.deflate);
|
||||
private inflateAsync = promisify(zlib.inflate);
|
||||
private brotliCompressAsync = promisify(zlib.brotliCompress);
|
||||
private brotliDecompressAsync = promisify(zlib.brotliDecompress);
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
constructor(config: Partial<CompressionConfig> = {}) {
|
||||
this.config = {
|
||||
algorithm: CompressionAlgorithm.GZIP,
|
||||
level: 6, // 平衡压缩率和速度
|
||||
threshold: 1024, // 1KB以上才压缩
|
||||
enableAsync: true,
|
||||
chunkSize: 64 * 1024, // 64KB分块
|
||||
...config
|
||||
};
|
||||
|
||||
this.stats = {
|
||||
totalCompressed: 0,
|
||||
totalDecompressed: 0,
|
||||
totalOriginalBytes: 0,
|
||||
totalCompressedBytes: 0,
|
||||
averageCompressionRatio: 0,
|
||||
averageCompressionTime: 0,
|
||||
averageDecompressionTime: 0,
|
||||
algorithmUsage: {
|
||||
[CompressionAlgorithm.NONE]: 0,
|
||||
[CompressionAlgorithm.GZIP]: 0,
|
||||
[CompressionAlgorithm.DEFLATE]: 0,
|
||||
[CompressionAlgorithm.BROTLI]: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩数据
|
||||
*/
|
||||
async compress(data: string | Buffer): Promise<CompressionResult> {
|
||||
const startTime = performance.now();
|
||||
const inputBuffer = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;
|
||||
const originalSize = inputBuffer.length;
|
||||
|
||||
try {
|
||||
// 检查是否需要压缩
|
||||
if (originalSize < this.config.threshold) {
|
||||
return this.createNoCompressionResult(inputBuffer, originalSize, startTime);
|
||||
}
|
||||
|
||||
let compressedData: Buffer;
|
||||
const algorithm = this.config.algorithm;
|
||||
|
||||
// 根据算法进行压缩
|
||||
switch (algorithm) {
|
||||
case CompressionAlgorithm.GZIP:
|
||||
compressedData = await this.compressGzip(inputBuffer);
|
||||
break;
|
||||
case CompressionAlgorithm.DEFLATE:
|
||||
compressedData = await this.compressDeflate(inputBuffer);
|
||||
break;
|
||||
case CompressionAlgorithm.BROTLI:
|
||||
compressedData = await this.compressBrotli(inputBuffer);
|
||||
break;
|
||||
case CompressionAlgorithm.NONE:
|
||||
default:
|
||||
return this.createNoCompressionResult(inputBuffer, originalSize, startTime);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const compressionTime = endTime - startTime;
|
||||
const compressedSize = compressedData.length;
|
||||
const compressionRatio = originalSize > 0 ? compressedSize / originalSize : 1;
|
||||
|
||||
// 检查压缩效果
|
||||
if (compressedSize >= originalSize * 0.9) {
|
||||
// 压缩效果不明显,返回原始数据
|
||||
this.logger.debug(`压缩效果不佳,返回原始数据。原始: ${originalSize}, 压缩: ${compressedSize}`);
|
||||
return this.createNoCompressionResult(inputBuffer, originalSize, startTime);
|
||||
}
|
||||
|
||||
const result: CompressionResult = {
|
||||
data: compressedData,
|
||||
originalSize,
|
||||
compressedSize,
|
||||
compressionRatio,
|
||||
compressionTime,
|
||||
algorithm
|
||||
};
|
||||
|
||||
// 更新统计
|
||||
this.updateCompressionStats(result);
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error('压缩失败:', error);
|
||||
return this.createNoCompressionResult(inputBuffer, originalSize, startTime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压缩数据
|
||||
*/
|
||||
async decompress(data: Buffer, algorithm: CompressionAlgorithm): Promise<Buffer> {
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
if (algorithm === CompressionAlgorithm.NONE) {
|
||||
return data;
|
||||
}
|
||||
|
||||
let decompressedData: Buffer;
|
||||
|
||||
switch (algorithm) {
|
||||
case CompressionAlgorithm.GZIP:
|
||||
decompressedData = await this.decompressGzip(data);
|
||||
break;
|
||||
case CompressionAlgorithm.DEFLATE:
|
||||
decompressedData = await this.decompressDeflate(data);
|
||||
break;
|
||||
case CompressionAlgorithm.BROTLI:
|
||||
decompressedData = await this.decompressBrotli(data);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`不支持的压缩算法: ${algorithm}`);
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const decompressionTime = endTime - startTime;
|
||||
|
||||
// 更新统计
|
||||
this.updateDecompressionStats(decompressionTime);
|
||||
|
||||
return decompressedData;
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error('解压缩失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量压缩
|
||||
*/
|
||||
async compressBatch(dataList: (string | Buffer)[]): Promise<CompressionResult[]> {
|
||||
const results: CompressionResult[] = [];
|
||||
|
||||
if (this.config.enableAsync) {
|
||||
// 并行压缩
|
||||
const promises = dataList.map(data => this.compress(data));
|
||||
return await Promise.all(promises);
|
||||
} else {
|
||||
// 串行压缩
|
||||
for (const data of dataList) {
|
||||
results.push(await this.compress(data));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自适应压缩
|
||||
* 根据数据特征自动选择最佳压缩算法
|
||||
*/
|
||||
async compressAdaptive(data: string | Buffer): Promise<CompressionResult> {
|
||||
const inputBuffer = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;
|
||||
const originalAlgorithm = this.config.algorithm;
|
||||
|
||||
try {
|
||||
// 对小数据进行算法测试
|
||||
const testSize = Math.min(inputBuffer.length, 4096); // 测试前4KB
|
||||
const testData = inputBuffer.subarray(0, testSize);
|
||||
|
||||
const algorithms = [
|
||||
CompressionAlgorithm.GZIP,
|
||||
CompressionAlgorithm.DEFLATE,
|
||||
CompressionAlgorithm.BROTLI
|
||||
];
|
||||
|
||||
let bestAlgorithm = CompressionAlgorithm.GZIP;
|
||||
let bestRatio = 1;
|
||||
|
||||
// 测试不同算法的压缩效果
|
||||
for (const algorithm of algorithms) {
|
||||
try {
|
||||
this.config.algorithm = algorithm;
|
||||
const testResult = await this.compress(testData);
|
||||
|
||||
if (testResult.compressionRatio < bestRatio) {
|
||||
bestRatio = testResult.compressionRatio;
|
||||
bestAlgorithm = algorithm;
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略测试失败的算法
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用最佳算法压缩完整数据
|
||||
this.config.algorithm = bestAlgorithm;
|
||||
const result = await this.compress(inputBuffer);
|
||||
|
||||
this.logger.debug(`自适应压缩选择算法: ${bestAlgorithm}, 压缩率: ${result.compressionRatio.toFixed(3)}`);
|
||||
|
||||
return result;
|
||||
|
||||
} finally {
|
||||
// 恢复原始配置
|
||||
this.config.algorithm = originalAlgorithm;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
getStats(): CompressionStats {
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
resetStats(): void {
|
||||
this.stats = {
|
||||
totalCompressed: 0,
|
||||
totalDecompressed: 0,
|
||||
totalOriginalBytes: 0,
|
||||
totalCompressedBytes: 0,
|
||||
averageCompressionRatio: 0,
|
||||
averageCompressionTime: 0,
|
||||
averageDecompressionTime: 0,
|
||||
algorithmUsage: {
|
||||
[CompressionAlgorithm.NONE]: 0,
|
||||
[CompressionAlgorithm.GZIP]: 0,
|
||||
[CompressionAlgorithm.DEFLATE]: 0,
|
||||
[CompressionAlgorithm.BROTLI]: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
updateConfig(newConfig: Partial<CompressionConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
this.logger.info('压缩器配置已更新:', newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取压缩建议
|
||||
*/
|
||||
getCompressionRecommendation(dataSize: number, dataType: string): CompressionAlgorithm {
|
||||
// 根据数据大小和类型推荐压缩算法
|
||||
if (dataSize < this.config.threshold) {
|
||||
return CompressionAlgorithm.NONE;
|
||||
}
|
||||
|
||||
if (dataType === 'json' || dataType === 'text') {
|
||||
// 文本数据推荐GZIP
|
||||
return CompressionAlgorithm.GZIP;
|
||||
} else if (dataType === 'binary') {
|
||||
// 二进制数据推荐DEFLATE
|
||||
return CompressionAlgorithm.DEFLATE;
|
||||
} else {
|
||||
// 默认推荐GZIP
|
||||
return CompressionAlgorithm.GZIP;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GZIP压缩
|
||||
*/
|
||||
private async compressGzip(data: Buffer): Promise<Buffer> {
|
||||
if (this.config.enableAsync) {
|
||||
return await this.gzipAsync(data, { level: this.config.level });
|
||||
} else {
|
||||
return zlib.gzipSync(data, { level: this.config.level });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GZIP解压缩
|
||||
*/
|
||||
private async decompressGzip(data: Buffer): Promise<Buffer> {
|
||||
if (this.config.enableAsync) {
|
||||
return await this.gunzipAsync(data);
|
||||
} else {
|
||||
return zlib.gunzipSync(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEFLATE压缩
|
||||
*/
|
||||
private async compressDeflate(data: Buffer): Promise<Buffer> {
|
||||
if (this.config.enableAsync) {
|
||||
return await this.deflateAsync(data, { level: this.config.level });
|
||||
} else {
|
||||
return zlib.deflateSync(data, { level: this.config.level });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEFLATE解压缩
|
||||
*/
|
||||
private async decompressDeflate(data: Buffer): Promise<Buffer> {
|
||||
if (this.config.enableAsync) {
|
||||
return await this.inflateAsync(data);
|
||||
} else {
|
||||
return zlib.inflateSync(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BROTLI压缩
|
||||
*/
|
||||
private async compressBrotli(data: Buffer): Promise<Buffer> {
|
||||
const options = {
|
||||
params: {
|
||||
[zlib.constants.BROTLI_PARAM_QUALITY]: this.config.level
|
||||
}
|
||||
};
|
||||
|
||||
if (this.config.enableAsync) {
|
||||
return await this.brotliCompressAsync(data, options);
|
||||
} else {
|
||||
return zlib.brotliCompressSync(data, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BROTLI解压缩
|
||||
*/
|
||||
private async decompressBrotli(data: Buffer): Promise<Buffer> {
|
||||
if (this.config.enableAsync) {
|
||||
return await this.brotliDecompressAsync(data);
|
||||
} else {
|
||||
return zlib.brotliDecompressSync(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建无压缩结果
|
||||
*/
|
||||
private createNoCompressionResult(
|
||||
data: Buffer,
|
||||
originalSize: number,
|
||||
startTime: number
|
||||
): CompressionResult {
|
||||
const endTime = performance.now();
|
||||
const result: CompressionResult = {
|
||||
data,
|
||||
originalSize,
|
||||
compressedSize: originalSize,
|
||||
compressionRatio: 1,
|
||||
compressionTime: endTime - startTime,
|
||||
algorithm: CompressionAlgorithm.NONE
|
||||
};
|
||||
|
||||
this.updateCompressionStats(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新压缩统计
|
||||
*/
|
||||
private updateCompressionStats(result: CompressionResult): void {
|
||||
this.stats.totalCompressed++;
|
||||
this.stats.totalOriginalBytes += result.originalSize;
|
||||
this.stats.totalCompressedBytes += result.compressedSize;
|
||||
this.stats.algorithmUsage[result.algorithm]++;
|
||||
|
||||
// 计算平均值
|
||||
this.stats.averageCompressionRatio =
|
||||
this.stats.totalOriginalBytes > 0 ?
|
||||
this.stats.totalCompressedBytes / this.stats.totalOriginalBytes : 1;
|
||||
|
||||
// 更新平均压缩时间(使用移动平均)
|
||||
const alpha = 0.1; // 平滑因子
|
||||
this.stats.averageCompressionTime =
|
||||
this.stats.averageCompressionTime * (1 - alpha) + result.compressionTime * alpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新解压缩统计
|
||||
*/
|
||||
private updateDecompressionStats(decompressionTime: number): void {
|
||||
this.stats.totalDecompressed++;
|
||||
|
||||
// 更新平均解压缩时间(使用移动平均)
|
||||
const alpha = 0.1;
|
||||
this.stats.averageDecompressionTime =
|
||||
this.stats.averageDecompressionTime * (1 - alpha) + decompressionTime * alpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取压缩效率报告
|
||||
*/
|
||||
getEfficiencyReport() {
|
||||
const savings = this.stats.totalOriginalBytes - this.stats.totalCompressedBytes;
|
||||
const savingsPercentage = this.stats.totalOriginalBytes > 0 ?
|
||||
(savings / this.stats.totalOriginalBytes) * 100 : 0;
|
||||
|
||||
return {
|
||||
totalSavings: savings,
|
||||
savingsPercentage,
|
||||
averageCompressionRatio: this.stats.averageCompressionRatio,
|
||||
averageCompressionTime: this.stats.averageCompressionTime,
|
||||
averageDecompressionTime: this.stats.averageDecompressionTime,
|
||||
algorithmUsage: this.stats.algorithmUsage,
|
||||
recommendation: this.generateRecommendation()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成优化建议
|
||||
*/
|
||||
private generateRecommendation(): string {
|
||||
const ratio = this.stats.averageCompressionRatio;
|
||||
const time = this.stats.averageCompressionTime;
|
||||
|
||||
if (ratio > 0.8) {
|
||||
return '压缩效果较差,建议调整算法或提高压缩级别';
|
||||
} else if (time > 50) {
|
||||
return '压缩时间较长,建议降低压缩级别或使用更快的算法';
|
||||
} else if (ratio < 0.3) {
|
||||
return '压缩效果很好,当前配置最优';
|
||||
} else {
|
||||
return '压缩性能正常';
|
||||
}
|
||||
}
|
||||
}
|
||||
461
packages/network-shared/src/transport/ErrorHandler.ts
Normal file
461
packages/network-shared/src/transport/ErrorHandler.ts
Normal file
@@ -0,0 +1,461 @@
|
||||
/**
|
||||
* 网络错误处理器
|
||||
* 提供统一的错误处理、分类和恢复策略
|
||||
*/
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { NetworkErrorType, INetworkError } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* 错误严重级别
|
||||
*/
|
||||
export enum ErrorSeverity {
|
||||
Low = 'low', // 低级错误,可以忽略
|
||||
Medium = 'medium', // 中级错误,需要记录但不影响功能
|
||||
High = 'high', // 高级错误,影响功能但可以恢复
|
||||
Critical = 'critical' // 严重错误,需要立即处理
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误恢复策略
|
||||
*/
|
||||
export enum RecoveryStrategy {
|
||||
Ignore = 'ignore', // 忽略错误
|
||||
Retry = 'retry', // 重试操作
|
||||
Reconnect = 'reconnect', // 重新连接
|
||||
Restart = 'restart', // 重启服务
|
||||
Escalate = 'escalate' // 上报错误
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误处理配置
|
||||
*/
|
||||
export interface ErrorHandlerConfig {
|
||||
maxRetryAttempts: number;
|
||||
retryDelay: number;
|
||||
enableAutoRecovery: boolean;
|
||||
enableErrorReporting: boolean;
|
||||
errorReportingEndpoint?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误统计信息
|
||||
*/
|
||||
export interface ErrorStats {
|
||||
totalErrors: number;
|
||||
errorsByType: Record<NetworkErrorType, number>;
|
||||
errorsBySeverity: Record<ErrorSeverity, number>;
|
||||
recoveredErrors: number;
|
||||
unrecoveredErrors: number;
|
||||
lastError?: INetworkError;
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误处理事件
|
||||
*/
|
||||
export interface ErrorHandlerEvents {
|
||||
errorOccurred: (error: INetworkError, severity: ErrorSeverity) => void;
|
||||
errorRecovered: (error: INetworkError, strategy: RecoveryStrategy) => void;
|
||||
errorUnrecoverable: (error: INetworkError) => void;
|
||||
criticalError: (error: INetworkError) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络错误处理器
|
||||
*/
|
||||
export class ErrorHandler {
|
||||
private logger = createLogger('ErrorHandler');
|
||||
private config: ErrorHandlerConfig;
|
||||
private stats: ErrorStats;
|
||||
private eventHandlers: Partial<ErrorHandlerEvents> = {};
|
||||
|
||||
// 错误恢复状态
|
||||
private retryAttempts: Map<string, number> = new Map();
|
||||
private pendingRecoveries: Set<string> = new Set();
|
||||
|
||||
// 错误分类规则
|
||||
private severityRules: Map<NetworkErrorType, ErrorSeverity> = new Map();
|
||||
private recoveryRules: Map<NetworkErrorType, RecoveryStrategy> = new Map();
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
constructor(config: Partial<ErrorHandlerConfig> = {}) {
|
||||
this.config = {
|
||||
maxRetryAttempts: 3,
|
||||
retryDelay: 1000,
|
||||
enableAutoRecovery: true,
|
||||
enableErrorReporting: false,
|
||||
...config
|
||||
};
|
||||
|
||||
this.stats = {
|
||||
totalErrors: 0,
|
||||
errorsByType: {} as Record<NetworkErrorType, number>,
|
||||
errorsBySeverity: {} as Record<ErrorSeverity, number>,
|
||||
recoveredErrors: 0,
|
||||
unrecoveredErrors: 0
|
||||
};
|
||||
|
||||
this.initializeDefaultRules();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理错误
|
||||
*/
|
||||
handleError(error: Error | INetworkError, context?: string): void {
|
||||
const networkError = this.normalizeError(error, context);
|
||||
const severity = this.classifyErrorSeverity(networkError);
|
||||
|
||||
// 更新统计
|
||||
this.updateStats(networkError, severity);
|
||||
|
||||
this.logger.error(`网络错误 [${severity}]: ${networkError.message}`, {
|
||||
type: networkError.type,
|
||||
code: networkError.code,
|
||||
details: networkError.details,
|
||||
context
|
||||
});
|
||||
|
||||
// 触发错误事件
|
||||
this.eventHandlers.errorOccurred?.(networkError, severity);
|
||||
|
||||
// 处理严重错误
|
||||
if (severity === ErrorSeverity.Critical) {
|
||||
this.eventHandlers.criticalError?.(networkError);
|
||||
}
|
||||
|
||||
// 尝试自动恢复
|
||||
if (this.config.enableAutoRecovery) {
|
||||
this.attemptRecovery(networkError, severity);
|
||||
}
|
||||
|
||||
// 错误报告
|
||||
if (this.config.enableErrorReporting) {
|
||||
this.reportError(networkError, severity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置错误分类规则
|
||||
*/
|
||||
setErrorSeverityRule(errorType: NetworkErrorType, severity: ErrorSeverity): void {
|
||||
this.severityRules.set(errorType, severity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置错误恢复策略
|
||||
*/
|
||||
setRecoveryStrategy(errorType: NetworkErrorType, strategy: RecoveryStrategy): void {
|
||||
this.recoveryRules.set(errorType, strategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误统计
|
||||
*/
|
||||
getStats(): ErrorStats {
|
||||
return { ...this.stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
resetStats(): void {
|
||||
this.stats = {
|
||||
totalErrors: 0,
|
||||
errorsByType: {} as Record<NetworkErrorType, number>,
|
||||
errorsBySeverity: {} as Record<ErrorSeverity, number>,
|
||||
recoveredErrors: 0,
|
||||
unrecoveredErrors: 0
|
||||
};
|
||||
this.retryAttempts.clear();
|
||||
this.pendingRecoveries.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置事件处理器
|
||||
*/
|
||||
on<K extends keyof ErrorHandlerEvents>(event: K, handler: ErrorHandlerEvents[K]): void {
|
||||
this.eventHandlers[event] = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件处理器
|
||||
*/
|
||||
off<K extends keyof ErrorHandlerEvents>(event: K): void {
|
||||
delete this.eventHandlers[event];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
updateConfig(newConfig: Partial<ErrorHandlerConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
this.logger.info('错误处理器配置已更新:', newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动标记错误已恢复
|
||||
*/
|
||||
markErrorRecovered(errorId: string): void {
|
||||
this.retryAttempts.delete(errorId);
|
||||
this.pendingRecoveries.delete(errorId);
|
||||
this.stats.recoveredErrors++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查错误是否可恢复
|
||||
*/
|
||||
isRecoverable(errorType: NetworkErrorType): boolean {
|
||||
const strategy = this.recoveryRules.get(errorType);
|
||||
return strategy !== undefined && strategy !== RecoveryStrategy.Ignore;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化错误对象
|
||||
*/
|
||||
private normalizeError(error: Error | INetworkError, context?: string): INetworkError {
|
||||
if ('type' in error && 'message' in error && 'timestamp' in error) {
|
||||
return error as INetworkError;
|
||||
}
|
||||
|
||||
// 将普通Error转换为INetworkError
|
||||
return {
|
||||
type: this.determineErrorType(error),
|
||||
message: error.message || '未知错误',
|
||||
code: (error as any).code,
|
||||
details: {
|
||||
context,
|
||||
stack: error.stack,
|
||||
name: error.name
|
||||
},
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定错误类型
|
||||
*/
|
||||
private determineErrorType(error: Error): NetworkErrorType {
|
||||
const message = error.message.toLowerCase();
|
||||
|
||||
if (message.includes('timeout')) {
|
||||
return NetworkErrorType.TIMEOUT;
|
||||
} else if (message.includes('connection')) {
|
||||
return NetworkErrorType.CONNECTION_LOST;
|
||||
} else if (message.includes('auth')) {
|
||||
return NetworkErrorType.AUTHENTICATION_FAILED;
|
||||
} else if (message.includes('permission')) {
|
||||
return NetworkErrorType.PERMISSION_DENIED;
|
||||
} else if (message.includes('rate') || message.includes('limit')) {
|
||||
return NetworkErrorType.RATE_LIMITED;
|
||||
} else if (message.includes('invalid') || message.includes('format')) {
|
||||
return NetworkErrorType.INVALID_MESSAGE;
|
||||
} else {
|
||||
return NetworkErrorType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类错误严重程度
|
||||
*/
|
||||
private classifyErrorSeverity(error: INetworkError): ErrorSeverity {
|
||||
// 使用自定义规则
|
||||
const customSeverity = this.severityRules.get(error.type);
|
||||
if (customSeverity) {
|
||||
return customSeverity;
|
||||
}
|
||||
|
||||
// 默认分类规则
|
||||
switch (error.type) {
|
||||
case NetworkErrorType.CONNECTION_FAILED:
|
||||
case NetworkErrorType.CONNECTION_LOST:
|
||||
return ErrorSeverity.High;
|
||||
|
||||
case NetworkErrorType.AUTHENTICATION_FAILED:
|
||||
case NetworkErrorType.PERMISSION_DENIED:
|
||||
return ErrorSeverity.Critical;
|
||||
|
||||
case NetworkErrorType.TIMEOUT:
|
||||
case NetworkErrorType.RATE_LIMITED:
|
||||
return ErrorSeverity.Medium;
|
||||
|
||||
case NetworkErrorType.INVALID_MESSAGE:
|
||||
return ErrorSeverity.Low;
|
||||
|
||||
default:
|
||||
return ErrorSeverity.Medium;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新统计信息
|
||||
*/
|
||||
private updateStats(error: INetworkError, severity: ErrorSeverity): void {
|
||||
this.stats.totalErrors++;
|
||||
this.stats.errorsByType[error.type] = (this.stats.errorsByType[error.type] || 0) + 1;
|
||||
this.stats.errorsBySeverity[severity] = (this.stats.errorsBySeverity[severity] || 0) + 1;
|
||||
this.stats.lastError = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试错误恢复
|
||||
*/
|
||||
private attemptRecovery(error: INetworkError, severity: ErrorSeverity): void {
|
||||
const strategy = this.recoveryRules.get(error.type);
|
||||
if (!strategy || strategy === RecoveryStrategy.Ignore) {
|
||||
return;
|
||||
}
|
||||
|
||||
const errorId = this.generateErrorId(error);
|
||||
|
||||
// 检查是否已经在恢复中
|
||||
if (this.pendingRecoveries.has(errorId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查重试次数
|
||||
const retryCount = this.retryAttempts.get(errorId) || 0;
|
||||
if (retryCount >= this.config.maxRetryAttempts) {
|
||||
this.stats.unrecoveredErrors++;
|
||||
this.eventHandlers.errorUnrecoverable?.(error);
|
||||
return;
|
||||
}
|
||||
|
||||
this.pendingRecoveries.add(errorId);
|
||||
this.retryAttempts.set(errorId, retryCount + 1);
|
||||
|
||||
this.logger.info(`尝试错误恢复: ${strategy} (第 ${retryCount + 1} 次)`, {
|
||||
errorType: error.type,
|
||||
strategy
|
||||
});
|
||||
|
||||
// 延迟执行恢复策略
|
||||
setTimeout(() => {
|
||||
this.executeRecoveryStrategy(error, strategy, errorId);
|
||||
}, this.config.retryDelay * (retryCount + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行恢复策略
|
||||
*/
|
||||
private executeRecoveryStrategy(
|
||||
error: INetworkError,
|
||||
strategy: RecoveryStrategy,
|
||||
errorId: string
|
||||
): void {
|
||||
try {
|
||||
switch (strategy) {
|
||||
case RecoveryStrategy.Retry:
|
||||
// 这里应该重试导致错误的操作
|
||||
// 具体实现需要外部提供重试回调
|
||||
break;
|
||||
|
||||
case RecoveryStrategy.Reconnect:
|
||||
// 这里应该触发重连
|
||||
// 具体实现需要外部处理
|
||||
break;
|
||||
|
||||
case RecoveryStrategy.Restart:
|
||||
// 这里应该重启相关服务
|
||||
// 具体实现需要外部处理
|
||||
break;
|
||||
|
||||
case RecoveryStrategy.Escalate:
|
||||
// 上报错误给上层处理
|
||||
this.logger.error('错误需要上层处理:', error);
|
||||
break;
|
||||
}
|
||||
|
||||
this.pendingRecoveries.delete(errorId);
|
||||
this.eventHandlers.errorRecovered?.(error, strategy);
|
||||
|
||||
} catch (recoveryError) {
|
||||
this.logger.error('错误恢复失败:', recoveryError);
|
||||
this.pendingRecoveries.delete(errorId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 报告错误
|
||||
*/
|
||||
private async reportError(error: INetworkError, severity: ErrorSeverity): Promise<void> {
|
||||
if (!this.config.errorReportingEndpoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const report = {
|
||||
error,
|
||||
severity,
|
||||
timestamp: Date.now(),
|
||||
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'Node.js',
|
||||
url: typeof window !== 'undefined' ? window.location.href : 'server'
|
||||
};
|
||||
|
||||
// 发送错误报告
|
||||
await fetch(this.config.errorReportingEndpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(report)
|
||||
});
|
||||
|
||||
} catch (reportError) {
|
||||
this.logger.error('发送错误报告失败:', reportError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成错误ID
|
||||
*/
|
||||
private generateErrorId(error: INetworkError): string {
|
||||
return `${error.type}-${error.code || 'no-code'}-${error.timestamp}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化默认规则
|
||||
*/
|
||||
private initializeDefaultRules(): void {
|
||||
// 默认严重程度规则
|
||||
this.severityRules.set(NetworkErrorType.CONNECTION_FAILED, ErrorSeverity.High);
|
||||
this.severityRules.set(NetworkErrorType.CONNECTION_LOST, ErrorSeverity.High);
|
||||
this.severityRules.set(NetworkErrorType.AUTHENTICATION_FAILED, ErrorSeverity.Critical);
|
||||
this.severityRules.set(NetworkErrorType.PERMISSION_DENIED, ErrorSeverity.Critical);
|
||||
this.severityRules.set(NetworkErrorType.TIMEOUT, ErrorSeverity.Medium);
|
||||
this.severityRules.set(NetworkErrorType.RATE_LIMITED, ErrorSeverity.Medium);
|
||||
this.severityRules.set(NetworkErrorType.INVALID_MESSAGE, ErrorSeverity.Low);
|
||||
|
||||
// 默认恢复策略
|
||||
this.recoveryRules.set(NetworkErrorType.CONNECTION_FAILED, RecoveryStrategy.Reconnect);
|
||||
this.recoveryRules.set(NetworkErrorType.CONNECTION_LOST, RecoveryStrategy.Reconnect);
|
||||
this.recoveryRules.set(NetworkErrorType.TIMEOUT, RecoveryStrategy.Retry);
|
||||
this.recoveryRules.set(NetworkErrorType.RATE_LIMITED, RecoveryStrategy.Retry);
|
||||
this.recoveryRules.set(NetworkErrorType.INVALID_MESSAGE, RecoveryStrategy.Ignore);
|
||||
this.recoveryRules.set(NetworkErrorType.AUTHENTICATION_FAILED, RecoveryStrategy.Escalate);
|
||||
this.recoveryRules.set(NetworkErrorType.PERMISSION_DENIED, RecoveryStrategy.Escalate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误趋势分析
|
||||
*/
|
||||
getErrorTrends() {
|
||||
const totalErrors = this.stats.totalErrors;
|
||||
if (totalErrors === 0) {
|
||||
return { trend: 'stable', recommendation: '系统运行正常' };
|
||||
}
|
||||
|
||||
const criticalRate = (this.stats.errorsBySeverity[ErrorSeverity.Critical] || 0) / totalErrors;
|
||||
const recoveryRate = this.stats.recoveredErrors / totalErrors;
|
||||
|
||||
if (criticalRate > 0.1) {
|
||||
return { trend: 'critical', recommendation: '存在严重错误,需要立即处理' };
|
||||
} else if (recoveryRate < 0.5) {
|
||||
return { trend: 'degrading', recommendation: '错误恢复率偏低,建议检查恢复策略' };
|
||||
} else if (totalErrors > 100) {
|
||||
return { trend: 'high_volume', recommendation: '错误量较大,建议分析根本原因' };
|
||||
} else {
|
||||
return { trend: 'stable', recommendation: '错误处理正常' };
|
||||
}
|
||||
}
|
||||
}
|
||||
381
packages/network-shared/src/transport/HeartbeatManager.ts
Normal file
381
packages/network-shared/src/transport/HeartbeatManager.ts
Normal file
@@ -0,0 +1,381 @@
|
||||
/**
|
||||
* 心跳管理器
|
||||
* 负责管理网络连接的心跳检测,包括延迟测算和连接健康检测
|
||||
*/
|
||||
import { createLogger } from '@esengine/ecs-framework';
|
||||
import { MessageType } from '../types/NetworkTypes';
|
||||
|
||||
/**
|
||||
* 心跳配置
|
||||
*/
|
||||
export interface HeartbeatConfig {
|
||||
interval: number; // 心跳间隔(毫秒)
|
||||
timeout: number; // 心跳超时(毫秒)
|
||||
maxMissedHeartbeats: number; // 最大丢失心跳数
|
||||
enableLatencyMeasurement: boolean; // 是否启用延迟测量
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳状态
|
||||
*/
|
||||
export interface HeartbeatStatus {
|
||||
isHealthy: boolean;
|
||||
lastHeartbeat: number;
|
||||
latency?: number;
|
||||
missedHeartbeats: number;
|
||||
averageLatency?: number;
|
||||
packetLoss?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳事件接口
|
||||
*/
|
||||
export interface HeartbeatEvents {
|
||||
heartbeatSent: (timestamp: number) => void;
|
||||
heartbeatReceived: (latency: number) => void;
|
||||
heartbeatTimeout: (missedCount: number) => void;
|
||||
healthStatusChanged: (isHealthy: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳消息接口
|
||||
*/
|
||||
export interface HeartbeatMessage {
|
||||
type: MessageType.HEARTBEAT;
|
||||
clientTime: number;
|
||||
serverTime?: number;
|
||||
sequence?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳管理器
|
||||
*/
|
||||
export class HeartbeatManager {
|
||||
private logger = createLogger('HeartbeatManager');
|
||||
private config: HeartbeatConfig;
|
||||
private status: HeartbeatStatus;
|
||||
private eventHandlers: Partial<HeartbeatEvents> = {};
|
||||
|
||||
// 定时器
|
||||
private heartbeatTimer?: number;
|
||||
private timeoutTimer?: number;
|
||||
|
||||
// 延迟测量
|
||||
private pendingPings: Map<number, number> = new Map();
|
||||
private latencyHistory: number[] = [];
|
||||
private sequence = 0;
|
||||
|
||||
// 统计信息
|
||||
private sentCount = 0;
|
||||
private receivedCount = 0;
|
||||
|
||||
/**
|
||||
* 发送心跳回调
|
||||
*/
|
||||
private sendHeartbeat?: (message: HeartbeatMessage) => void;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
constructor(config: Partial<HeartbeatConfig> = {}) {
|
||||
this.config = {
|
||||
interval: 30000, // 30秒
|
||||
timeout: 60000, // 60秒
|
||||
maxMissedHeartbeats: 3, // 最大丢失3次
|
||||
enableLatencyMeasurement: true,
|
||||
...config
|
||||
};
|
||||
|
||||
this.status = {
|
||||
isHealthy: true,
|
||||
lastHeartbeat: Date.now(),
|
||||
missedHeartbeats: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动心跳
|
||||
*/
|
||||
start(sendCallback: (message: HeartbeatMessage) => void): void {
|
||||
this.sendHeartbeat = sendCallback;
|
||||
this.startHeartbeatTimer();
|
||||
this.logger.info('心跳管理器已启动');
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止心跳
|
||||
*/
|
||||
stop(): void {
|
||||
this.stopHeartbeatTimer();
|
||||
this.stopTimeoutTimer();
|
||||
this.pendingPings.clear();
|
||||
this.logger.info('心跳管理器已停止');
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理接收到的心跳响应
|
||||
*/
|
||||
handleHeartbeatResponse(message: HeartbeatMessage): void {
|
||||
const now = Date.now();
|
||||
this.status.lastHeartbeat = now;
|
||||
this.receivedCount++;
|
||||
|
||||
// 重置丢失心跳计数
|
||||
this.status.missedHeartbeats = 0;
|
||||
|
||||
// 计算延迟
|
||||
if (this.config.enableLatencyMeasurement && message.sequence !== undefined) {
|
||||
const sentTime = this.pendingPings.get(message.sequence);
|
||||
if (sentTime) {
|
||||
const latency = now - sentTime;
|
||||
this.updateLatency(latency);
|
||||
this.pendingPings.delete(message.sequence);
|
||||
|
||||
this.eventHandlers.heartbeatReceived?.(latency);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新健康状态
|
||||
this.updateHealthStatus(true);
|
||||
|
||||
// 停止超时定时器
|
||||
this.stopTimeoutTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理心跳超时
|
||||
*/
|
||||
handleHeartbeatTimeout(): void {
|
||||
this.status.missedHeartbeats++;
|
||||
this.logger.warn(`心跳超时,丢失次数: ${this.status.missedHeartbeats}`);
|
||||
|
||||
// 触发超时事件
|
||||
this.eventHandlers.heartbeatTimeout?.(this.status.missedHeartbeats);
|
||||
|
||||
// 检查是否达到最大丢失次数
|
||||
if (this.status.missedHeartbeats >= this.config.maxMissedHeartbeats) {
|
||||
this.updateHealthStatus(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取心跳状态
|
||||
*/
|
||||
getStatus(): HeartbeatStatus {
|
||||
return { ...this.status };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
getStats() {
|
||||
const packetLoss = this.sentCount > 0 ?
|
||||
((this.sentCount - this.receivedCount) / this.sentCount) * 100 : 0;
|
||||
|
||||
return {
|
||||
sentCount: this.sentCount,
|
||||
receivedCount: this.receivedCount,
|
||||
packetLoss,
|
||||
averageLatency: this.status.averageLatency,
|
||||
currentLatency: this.status.latency,
|
||||
isHealthy: this.status.isHealthy,
|
||||
missedHeartbeats: this.status.missedHeartbeats,
|
||||
latencyHistory: [...this.latencyHistory]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置事件处理器
|
||||
*/
|
||||
on<K extends keyof HeartbeatEvents>(event: K, handler: HeartbeatEvents[K]): void {
|
||||
this.eventHandlers[event] = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件处理器
|
||||
*/
|
||||
off<K extends keyof HeartbeatEvents>(event: K): void {
|
||||
delete this.eventHandlers[event];
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动发送心跳
|
||||
*/
|
||||
sendHeartbeatNow(): void {
|
||||
this.doSendHeartbeat();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
*/
|
||||
updateConfig(newConfig: Partial<HeartbeatConfig>): void {
|
||||
Object.assign(this.config, newConfig);
|
||||
this.logger.info('心跳配置已更新:', newConfig);
|
||||
|
||||
// 重启定时器以应用新配置
|
||||
if (this.heartbeatTimer) {
|
||||
this.stop();
|
||||
if (this.sendHeartbeat) {
|
||||
this.start(this.sendHeartbeat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动心跳定时器
|
||||
*/
|
||||
private startHeartbeatTimer(): void {
|
||||
this.heartbeatTimer = window.setInterval(() => {
|
||||
this.doSendHeartbeat();
|
||||
}, this.config.interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止心跳定时器
|
||||
*/
|
||||
private stopHeartbeatTimer(): void {
|
||||
if (this.heartbeatTimer) {
|
||||
clearInterval(this.heartbeatTimer);
|
||||
this.heartbeatTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动超时定时器
|
||||
*/
|
||||
private startTimeoutTimer(): void {
|
||||
this.timeoutTimer = window.setTimeout(() => {
|
||||
this.handleHeartbeatTimeout();
|
||||
}, this.config.timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止超时定时器
|
||||
*/
|
||||
private stopTimeoutTimer(): void {
|
||||
if (this.timeoutTimer) {
|
||||
clearTimeout(this.timeoutTimer);
|
||||
this.timeoutTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行发送心跳
|
||||
*/
|
||||
private doSendHeartbeat(): void {
|
||||
if (!this.sendHeartbeat) {
|
||||
this.logger.error('心跳发送回调未设置');
|
||||
return;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const sequence = this.config.enableLatencyMeasurement ? ++this.sequence : undefined;
|
||||
|
||||
const message: HeartbeatMessage = {
|
||||
type: MessageType.HEARTBEAT,
|
||||
clientTime: now,
|
||||
sequence
|
||||
};
|
||||
|
||||
try {
|
||||
this.sendHeartbeat(message);
|
||||
this.sentCount++;
|
||||
|
||||
// 记录发送时间用于延迟计算
|
||||
if (sequence !== undefined) {
|
||||
this.pendingPings.set(sequence, now);
|
||||
|
||||
// 清理过期的pending pings
|
||||
this.cleanupPendingPings();
|
||||
}
|
||||
|
||||
// 启动超时定时器
|
||||
this.stopTimeoutTimer();
|
||||
this.startTimeoutTimer();
|
||||
|
||||
this.eventHandlers.heartbeatSent?.(now);
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error('发送心跳失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新延迟信息
|
||||
*/
|
||||
private updateLatency(latency: number): void {
|
||||
this.status.latency = latency;
|
||||
|
||||
// 保存延迟历史(最多100个样本)
|
||||
this.latencyHistory.push(latency);
|
||||
if (this.latencyHistory.length > 100) {
|
||||
this.latencyHistory.shift();
|
||||
}
|
||||
|
||||
// 计算平均延迟
|
||||
this.status.averageLatency = this.latencyHistory.reduce((sum, lat) => sum + lat, 0) / this.latencyHistory.length;
|
||||
|
||||
this.logger.debug(`延迟更新: ${latency}ms, 平均: ${this.status.averageLatency?.toFixed(1)}ms`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新健康状态
|
||||
*/
|
||||
private updateHealthStatus(isHealthy: boolean): void {
|
||||
if (this.status.isHealthy !== isHealthy) {
|
||||
this.status.isHealthy = isHealthy;
|
||||
this.logger.info(`连接健康状态变更: ${isHealthy ? '健康' : '不健康'}`);
|
||||
this.eventHandlers.healthStatusChanged?.(isHealthy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期的pending pings
|
||||
*/
|
||||
private cleanupPendingPings(): void {
|
||||
const now = Date.now();
|
||||
const timeout = this.config.timeout * 2; // 清理超过2倍超时时间的记录
|
||||
|
||||
for (const [sequence, sentTime] of this.pendingPings) {
|
||||
if (now - sentTime > timeout) {
|
||||
this.pendingPings.delete(sequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
resetStats(): void {
|
||||
this.sentCount = 0;
|
||||
this.receivedCount = 0;
|
||||
this.latencyHistory.length = 0;
|
||||
this.status.averageLatency = undefined;
|
||||
this.status.latency = undefined;
|
||||
this.status.missedHeartbeats = 0;
|
||||
this.pendingPings.clear();
|
||||
this.logger.info('心跳统计信息已重置');
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查连接是否健康
|
||||
*/
|
||||
isConnectionHealthy(): boolean {
|
||||
const now = Date.now();
|
||||
const timeSinceLastHeartbeat = now - this.status.lastHeartbeat;
|
||||
|
||||
return this.status.isHealthy &&
|
||||
timeSinceLastHeartbeat <= this.config.timeout &&
|
||||
this.status.missedHeartbeats < this.config.maxMissedHeartbeats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取建议的重连延迟
|
||||
*/
|
||||
getReconnectDelay(): number {
|
||||
// 基于丢失心跳次数计算重连延迟
|
||||
const baseDelay = this.config.interval;
|
||||
const multiplier = Math.min(Math.pow(2, this.status.missedHeartbeats), 8);
|
||||
return baseDelay * multiplier;
|
||||
}
|
||||
}
|
||||
@@ -23,14 +23,14 @@ export interface ITransport {
|
||||
* @param clientId 客户端ID
|
||||
* @param data 数据
|
||||
*/
|
||||
send(clientId: string, data: Buffer | string): void;
|
||||
send(clientId: string, data: ArrayBuffer | string): void;
|
||||
|
||||
/**
|
||||
* 广播数据到所有客户端
|
||||
* @param data 数据
|
||||
* @param exclude 排除的客户端ID列表
|
||||
*/
|
||||
broadcast(data: Buffer | string, exclude?: string[]): void;
|
||||
broadcast(data: ArrayBuffer | string, exclude?: string[]): void;
|
||||
|
||||
/**
|
||||
* 监听客户端连接事件
|
||||
@@ -48,7 +48,7 @@ export interface ITransport {
|
||||
* 监听消息接收事件
|
||||
* @param handler 处理函数
|
||||
*/
|
||||
onMessage(handler: (clientId: string, data: Buffer | string) => void): void;
|
||||
onMessage(handler: (clientId: string, data: ArrayBuffer | string) => void): void;
|
||||
|
||||
/**
|
||||
* 监听错误事件
|
||||
@@ -96,13 +96,13 @@ export interface IClientTransport {
|
||||
* 发送数据到服务器
|
||||
* @param data 数据
|
||||
*/
|
||||
send(data: Buffer | string): void;
|
||||
send(data: ArrayBuffer | string): void;
|
||||
|
||||
/**
|
||||
* 监听服务器消息
|
||||
* @param handler 处理函数
|
||||
*/
|
||||
onMessage(handler: (data: Buffer | string) => void): void;
|
||||
onMessage(handler: (data: ArrayBuffer | string) => void): void;
|
||||
|
||||
/**
|
||||
* 监听连接状态变化
|
||||
|
||||
85
packages/network-shared/src/utils/EventEmitter.ts
Normal file
85
packages/network-shared/src/utils/EventEmitter.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 网络层专用的EventEmitter实现
|
||||
* 继承自core库的Emitter,提供简单的事件API
|
||||
*/
|
||||
import { Emitter } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* 网络事件发射器,专为网络层设计
|
||||
* 使用字符串或symbol作为事件类型,简化API
|
||||
*/
|
||||
export class EventEmitter extends Emitter<string | symbol, void> {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加事件监听器
|
||||
* @param event 事件名称
|
||||
* @param listener 监听函数
|
||||
*/
|
||||
public on(event: string | symbol, listener: Function): this {
|
||||
this.addObserver(event, listener, undefined as void);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一次性事件监听器
|
||||
* @param event 事件名称
|
||||
* @param listener 监听函数
|
||||
*/
|
||||
public once(event: string | symbol, listener: Function): this {
|
||||
const onceWrapper = (...args: any[]) => {
|
||||
listener.apply(this, args);
|
||||
this.removeObserver(event, onceWrapper);
|
||||
};
|
||||
this.addObserver(event, onceWrapper, undefined as void);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听器
|
||||
* @param event 事件名称
|
||||
* @param listener 监听函数,不传则移除所有
|
||||
*/
|
||||
public off(event: string | symbol, listener?: Function): this {
|
||||
if (listener) {
|
||||
this.removeObserver(event, listener);
|
||||
} else {
|
||||
this.removeAllObservers(event);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听器(别名)
|
||||
*/
|
||||
public removeListener(event: string | symbol, listener: Function): this {
|
||||
return this.off(event, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除所有监听器
|
||||
*/
|
||||
public removeAllListeners(event?: string | symbol): this {
|
||||
this.removeAllObservers(event);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取监听器数量
|
||||
*/
|
||||
public listenerCount(event: string | symbol): number {
|
||||
return this.getObserverCount(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发射事件(兼容Node.js EventEmitter)
|
||||
* @param event 事件名称
|
||||
* @param args 事件参数
|
||||
*/
|
||||
public override emit(event: string | symbol, ...args: any[]): boolean {
|
||||
super.emit(event, ...args);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
4
packages/network-shared/src/utils/index.ts
Normal file
4
packages/network-shared/src/utils/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 网络层工具类
|
||||
*/
|
||||
export * from './EventEmitter';
|
||||
Reference in New Issue
Block a user