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

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

View File

@@ -0,0 +1,410 @@
/**
* 服务端连接管理器
* 负责管理所有客户端连接的生命周期
*/
import { createLogger, ITimer, Core } from '@esengine/ecs-framework';
import {
ITransportClientInfo,
ConnectionState,
IConnectionStats,
EventEmitter
} from '@esengine/network-shared';
/**
* 客户端会话信息
*/
export interface ClientSession {
id: string;
info: ITransportClientInfo;
state: ConnectionState;
lastHeartbeat: number;
stats: IConnectionStats;
authenticated: boolean;
roomId?: string;
userData?: Record<string, any>;
}
/**
* 连接管理器配置
*/
export interface ConnectionManagerConfig {
heartbeatInterval: number;
heartbeatTimeout: number;
maxIdleTime: number;
cleanupInterval: number;
}
/**
* 连接管理器
*/
export class ConnectionManager extends EventEmitter {
private logger = createLogger('ConnectionManager');
private sessions: Map<string, ClientSession> = new Map();
private config: ConnectionManagerConfig;
private heartbeatTimer?: ITimer;
private cleanupTimer?: ITimer;
/**
* 构造函数
*/
constructor(config: Partial<ConnectionManagerConfig> = {}) {
super();
this.config = {
heartbeatInterval: 30000, // 30秒心跳间隔
heartbeatTimeout: 60000, // 60秒心跳超时
maxIdleTime: 300000, // 5分钟最大空闲时间
cleanupInterval: 60000, // 1分钟清理间隔
...config
};
}
/**
* 启动连接管理器
*/
start(): void {
this.logger.info('连接管理器启动');
this.startHeartbeatTimer();
this.startCleanupTimer();
}
/**
* 停止连接管理器
*/
stop(): void {
this.logger.info('连接管理器停止');
this.stopHeartbeatTimer();
this.stopCleanupTimer();
this.sessions.clear();
}
/**
* 添加客户端会话
*/
addSession(clientInfo: ITransportClientInfo): ClientSession {
const session: ClientSession = {
id: clientInfo.id,
info: clientInfo,
state: ConnectionState.Connected,
lastHeartbeat: Date.now(),
authenticated: false,
stats: {
state: ConnectionState.Connected,
connectTime: clientInfo.connectTime,
reconnectCount: 0,
bytesSent: 0,
bytesReceived: 0,
messagesSent: 0,
messagesReceived: 0
}
};
this.sessions.set(clientInfo.id, session);
this.logger.info(`添加客户端会话: ${clientInfo.id}`);
this.emit('sessionAdded', session);
return session;
}
/**
* 移除客户端会话
*/
removeSession(clientId: string, reason?: string): boolean {
const session = this.sessions.get(clientId);
if (!session) {
return false;
}
session.state = ConnectionState.Disconnected;
session.stats.disconnectTime = Date.now();
this.sessions.delete(clientId);
this.logger.info(`移除客户端会话: ${clientId}, 原因: ${reason || '未知'}`);
this.emit('sessionRemoved', session, reason);
return true;
}
/**
* 获取客户端会话
*/
getSession(clientId: string): ClientSession | undefined {
return this.sessions.get(clientId);
}
/**
* 获取所有客户端会话
*/
getAllSessions(): ClientSession[] {
return Array.from(this.sessions.values());
}
/**
* 获取已认证的会话
*/
getAuthenticatedSessions(): ClientSession[] {
return Array.from(this.sessions.values()).filter(session => session.authenticated);
}
/**
* 获取指定房间的会话
*/
getSessionsByRoom(roomId: string): ClientSession[] {
return Array.from(this.sessions.values()).filter(session => session.roomId === roomId);
}
/**
* 更新会话心跳时间
*/
updateHeartbeat(clientId: string): boolean {
const session = this.sessions.get(clientId);
if (!session) {
return false;
}
session.lastHeartbeat = Date.now();
return true;
}
/**
* 设置会话认证状态
*/
setSessionAuthenticated(clientId: string, authenticated: boolean): boolean {
const session = this.sessions.get(clientId);
if (!session) {
return false;
}
const wasAuthenticated = session.authenticated;
session.authenticated = authenticated;
if (wasAuthenticated !== authenticated) {
this.emit('sessionAuthChanged', session, authenticated);
this.logger.info(`客户端 ${clientId} 认证状态变更: ${authenticated}`);
}
return true;
}
/**
* 设置会话所在房间
*/
setSessionRoom(clientId: string, roomId?: string): boolean {
const session = this.sessions.get(clientId);
if (!session) {
return false;
}
const oldRoomId = session.roomId;
session.roomId = roomId;
if (oldRoomId !== roomId) {
this.emit('sessionRoomChanged', session, oldRoomId, roomId);
this.logger.info(`客户端 ${clientId} 房间变更: ${oldRoomId} -> ${roomId}`);
}
return true;
}
/**
* 更新会话数据统计
*/
updateSessionStats(clientId: string, stats: Partial<IConnectionStats>): boolean {
const session = this.sessions.get(clientId);
if (!session) {
return false;
}
Object.assign(session.stats, stats);
return true;
}
/**
* 设置会话用户数据
*/
setSessionUserData(clientId: string, userData: Record<string, any>): boolean {
const session = this.sessions.get(clientId);
if (!session) {
return false;
}
session.userData = { ...session.userData, ...userData };
return true;
}
/**
* 检查会话是否存活
*/
isSessionAlive(clientId: string): boolean {
const session = this.sessions.get(clientId);
if (!session) {
return false;
}
const now = Date.now();
return (now - session.lastHeartbeat) <= this.config.heartbeatTimeout;
}
/**
* 获取超时的会话
*/
getTimeoutSessions(): ClientSession[] {
const now = Date.now();
return Array.from(this.sessions.values()).filter(session => {
return (now - session.lastHeartbeat) > this.config.heartbeatTimeout;
});
}
/**
* 获取空闲的会话
*/
getIdleSessions(): ClientSession[] {
const now = Date.now();
return Array.from(this.sessions.values()).filter(session => {
return (now - session.lastHeartbeat) > this.config.maxIdleTime;
});
}
/**
* 获取连接统计信息
*/
getConnectionStats() {
const allSessions = this.getAllSessions();
const authenticatedSessions = this.getAuthenticatedSessions();
const timeoutSessions = this.getTimeoutSessions();
return {
totalConnections: allSessions.length,
authenticatedConnections: authenticatedSessions.length,
timeoutConnections: timeoutSessions.length,
averageLatency: this.calculateAverageLatency(allSessions),
connectionsByRoom: this.getConnectionsByRoom(),
totalBytesSent: allSessions.reduce((sum, s) => sum + s.stats.bytesSent, 0),
totalBytesReceived: allSessions.reduce((sum, s) => sum + s.stats.bytesReceived, 0),
totalMessagesSent: allSessions.reduce((sum, s) => sum + s.stats.messagesSent, 0),
totalMessagesReceived: allSessions.reduce((sum, s) => sum + s.stats.messagesReceived, 0)
};
}
/**
* 计算平均延迟
*/
private calculateAverageLatency(sessions: ClientSession[]): number {
const validLatencies = sessions
.map(s => s.stats.latency)
.filter(latency => latency !== undefined) as number[];
if (validLatencies.length === 0) return 0;
return validLatencies.reduce((sum, latency) => sum + latency, 0) / validLatencies.length;
}
/**
* 按房间统计连接数
*/
private getConnectionsByRoom(): Record<string, number> {
const roomCounts: Record<string, number> = {};
for (const session of this.sessions.values()) {
const roomId = session.roomId || 'lobby';
roomCounts[roomId] = (roomCounts[roomId] || 0) + 1;
}
return roomCounts;
}
/**
* 启动心跳定时器
*/
private startHeartbeatTimer(): void {
this.heartbeatTimer = Core.schedule(this.config.heartbeatInterval / 1000, true, this, () => {
this.checkHeartbeats();
});
}
/**
* 停止心跳定时器
*/
private stopHeartbeatTimer(): void {
if (this.heartbeatTimer) {
this.heartbeatTimer.stop();
this.heartbeatTimer = undefined;
}
}
/**
* 启动清理定时器
*/
private startCleanupTimer(): void {
this.cleanupTimer = Core.schedule(this.config.cleanupInterval / 1000, true, this, () => {
this.performCleanup();
});
}
/**
* 停止清理定时器
*/
private stopCleanupTimer(): void {
if (this.cleanupTimer) {
this.cleanupTimer.stop();
this.cleanupTimer = undefined;
}
}
/**
* 检查心跳超时
*/
private checkHeartbeats(): void {
const timeoutSessions = this.getTimeoutSessions();
for (const session of timeoutSessions) {
this.logger.warn(`客户端心跳超时: ${session.id}`);
this.emit('heartbeatTimeout', session);
// 可以选择断开超时的连接
// this.removeSession(session.id, '心跳超时');
}
if (timeoutSessions.length > 0) {
this.logger.warn(`发现 ${timeoutSessions.length} 个心跳超时的连接`);
}
}
/**
* 执行清理操作
*/
private performCleanup(): void {
const idleSessions = this.getIdleSessions();
for (const session of idleSessions) {
this.logger.info(`清理空闲连接: ${session.id}`);
this.removeSession(session.id, '空闲超时');
}
if (idleSessions.length > 0) {
this.logger.info(`清理了 ${idleSessions.length} 个空闲连接`);
}
}
/**
* 批量操作:踢出指定房间的所有客户端
*/
kickRoomClients(roomId: string, reason?: string): number {
const roomSessions = this.getSessionsByRoom(roomId);
for (const session of roomSessions) {
this.removeSession(session.id, reason || '房间解散');
}
this.logger.info(`踢出房间 ${roomId}${roomSessions.length} 个客户端`);
return roomSessions.length;
}
/**
* 批量操作:向指定房间广播消息(这里只返回会话列表)
*/
getRoomSessionsForBroadcast(roomId: string, excludeClientId?: string): ClientSession[] {
return this.getSessionsByRoom(roomId).filter(session =>
session.id !== excludeClientId && session.authenticated
);
}
}

View File

@@ -0,0 +1,701 @@
/**
* 网络服务器核心类
* 负责服务器的启动/停止、传输层管理和客户端会话管理
*/
import { createLogger, Core } from '@esengine/ecs-framework';
import {
ITransportConfig,
MessageType,
INetworkMessage,
IConnectMessage,
IConnectResponseMessage,
IHeartbeatMessage,
NetworkErrorType,
EventEmitter
} from '@esengine/network-shared';
import { WebSocketTransport } from '../transport/WebSocketTransport';
import { ConnectionManager, ClientSession } from './ConnectionManager';
import { JSONSerializer } from '@esengine/network-shared';
import { MessageManager } from '@esengine/network-shared';
import { ErrorHandler } from '@esengine/network-shared';
/**
* 网络服务器配置
*/
export interface NetworkServerConfig {
transport: ITransportConfig;
authentication: {
required: boolean;
timeout: number;
maxAttempts: number;
};
rateLimit: {
enabled: boolean;
maxRequestsPerMinute: number;
banDuration: number;
};
features: {
enableCompression: boolean;
enableHeartbeat: boolean;
enableRooms: boolean;
enableMetrics: boolean;
};
}
/**
* 服务器状态
*/
export enum ServerState {
Stopped = 'stopped',
Starting = 'starting',
Running = 'running',
Stopping = 'stopping',
Error = 'error'
}
/**
* 服务器统计信息
*/
export interface ServerStats {
state: ServerState;
uptime: number;
startTime?: number;
connections: {
total: number;
authenticated: number;
peak: number;
};
messages: {
sent: number;
received: number;
errors: number;
};
bandwidth: {
inbound: number;
outbound: number;
};
}
/**
* 网络服务器事件接口
*/
export interface NetworkServerEvents {
serverStarted: (port: number) => void;
serverStopped: () => void;
serverError: (error: Error) => void;
clientConnected: (session: ClientSession) => void;
clientDisconnected: (session: ClientSession, reason?: string) => void;
clientAuthenticated: (session: ClientSession) => void;
messageReceived: (session: ClientSession, message: INetworkMessage) => void;
messageSent: (session: ClientSession, message: INetworkMessage) => void;
}
/**
* 网络服务器核心实现
*/
export class NetworkServer extends EventEmitter {
private logger = createLogger('NetworkServer');
private config: NetworkServerConfig;
private state: ServerState = ServerState.Stopped;
private stats: ServerStats;
// 核心组件
private transport?: WebSocketTransport;
private connectionManager: ConnectionManager;
private serializer: JSONSerializer;
private messageManager: MessageManager;
private errorHandler: ErrorHandler;
// 事件处理器
private eventHandlers: Partial<NetworkServerEvents> = {};
// 速率限制
private rateLimitMap: Map<string, { count: number; resetTime: number; banned: boolean }> = new Map();
/**
* 构造函数
*/
constructor(config: Partial<NetworkServerConfig> = {}) {
super();
this.config = {
transport: {
port: 8080,
host: '0.0.0.0',
maxConnections: 1000,
heartbeatInterval: 30000,
connectionTimeout: 60000,
maxMessageSize: 1024 * 1024,
compression: true,
...config.transport
},
authentication: {
required: false,
timeout: 30000,
maxAttempts: 3,
...config.authentication
},
rateLimit: {
enabled: true,
maxRequestsPerMinute: 100,
banDuration: 300000, // 5分钟
...config.rateLimit
},
features: {
enableCompression: true,
enableHeartbeat: true,
enableRooms: true,
enableMetrics: true,
...config.features
}
};
this.stats = {
state: ServerState.Stopped,
uptime: 0,
connections: {
total: 0,
authenticated: 0,
peak: 0
},
messages: {
sent: 0,
received: 0,
errors: 0
},
bandwidth: {
inbound: 0,
outbound: 0
}
};
// 初始化核心组件
this.connectionManager = new ConnectionManager({
heartbeatInterval: this.config.transport.heartbeatInterval,
heartbeatTimeout: this.config.transport.connectionTimeout
});
this.serializer = new JSONSerializer({
enableTypeChecking: true,
enableCompression: this.config.features.enableCompression,
maxMessageSize: this.config.transport.maxMessageSize
});
this.messageManager = new MessageManager({
enableTimestampValidation: true,
enableMessageDeduplication: true
});
this.errorHandler = new ErrorHandler({
maxRetryAttempts: 3,
enableAutoRecovery: true
});
this.setupEventHandlers();
}
/**
* 启动服务器
*/
async start(): Promise<void> {
if (this.state !== ServerState.Stopped) {
throw new Error(`服务器状态错误: ${this.state}`);
}
this.setState(ServerState.Starting);
this.logger.info('正在启动网络服务器...');
try {
// 创建传输层
this.transport = new WebSocketTransport(this.config.transport);
this.setupTransportEvents();
// 启动传输层
await this.transport.start(
this.config.transport.port,
this.config.transport.host
);
// 启动连接管理器
this.connectionManager.start();
// 记录启动时间
this.stats.startTime = Date.now();
this.setState(ServerState.Running);
this.logger.info(`网络服务器已启动: ${this.config.transport.host}:${this.config.transport.port}`);
this.eventHandlers.serverStarted?.(this.config.transport.port);
} catch (error) {
this.setState(ServerState.Error);
this.logger.error('启动网络服务器失败:', error);
this.eventHandlers.serverError?.(error as Error);
throw error;
}
}
/**
* 停止服务器
*/
async stop(): Promise<void> {
if (this.state === ServerState.Stopped) {
return;
}
this.setState(ServerState.Stopping);
this.logger.info('正在停止网络服务器...');
try {
// 停止连接管理器
this.connectionManager.stop();
// 停止传输层
if (this.transport) {
await this.transport.stop();
this.transport = undefined;
}
// 清理速率限制数据
this.rateLimitMap.clear();
this.setState(ServerState.Stopped);
this.logger.info('网络服务器已停止');
this.eventHandlers.serverStopped?.();
} catch (error) {
this.logger.error('停止网络服务器失败:', error);
this.eventHandlers.serverError?.(error as Error);
throw error;
}
}
/**
* 发送消息到指定客户端
*/
sendToClient<T extends INetworkMessage>(clientId: string, message: T): boolean {
if (!this.transport || this.state !== ServerState.Running) {
this.logger.warn('服务器未运行,无法发送消息');
return false;
}
const session = this.connectionManager.getSession(clientId);
if (!session) {
this.logger.warn(`客户端会话不存在: ${clientId}`);
return false;
}
try {
const serializedMessage = this.serializer.serialize(message);
this.transport.send(clientId, serializedMessage.data);
// 更新统计
this.stats.messages.sent++;
this.stats.bandwidth.outbound += serializedMessage.size;
this.eventHandlers.messageSent?.(session, message);
return true;
} catch (error) {
this.logger.error(`发送消息到客户端 ${clientId} 失败:`, error);
this.stats.messages.errors++;
this.errorHandler.handleError(error as Error, `sendToClient:${clientId}`);
return false;
}
}
/**
* 广播消息到所有客户端
*/
broadcast<T extends INetworkMessage>(message: T, exclude?: string[]): number {
if (!this.transport || this.state !== ServerState.Running) {
this.logger.warn('服务器未运行,无法广播消息');
return 0;
}
try {
const serializedMessage = this.serializer.serialize(message);
this.transport.broadcast(serializedMessage.data, exclude);
const clientCount = this.connectionManager.getAllSessions().length - (exclude?.length || 0);
// 更新统计
this.stats.messages.sent += clientCount;
this.stats.bandwidth.outbound += serializedMessage.size * clientCount;
return clientCount;
} catch (error) {
this.logger.error('广播消息失败:', error);
this.stats.messages.errors++;
this.errorHandler.handleError(error as Error, 'broadcast');
return 0;
}
}
/**
* 踢出客户端
*/
kickClient(clientId: string, reason?: string): boolean {
const session = this.connectionManager.getSession(clientId);
if (!session) {
return false;
}
if (this.transport) {
this.transport.disconnectClient(clientId, reason);
}
return this.connectionManager.removeSession(clientId, reason);
}
/**
* 获取服务器状态
*/
getState(): ServerState {
return this.state;
}
/**
* 检查服务器是否正在运行
*/
isRunning(): boolean {
return this.state === ServerState.Running;
}
/**
* 获取服务器统计信息
*/
getStats(): ServerStats {
const currentStats = { ...this.stats };
if (this.stats.startTime) {
currentStats.uptime = Date.now() - this.stats.startTime;
}
const connectionStats = this.connectionManager.getConnectionStats();
currentStats.connections.total = connectionStats.totalConnections;
currentStats.connections.authenticated = connectionStats.authenticatedConnections;
return currentStats;
}
/**
* 获取所有客户端会话
*/
getAllSessions(): ClientSession[] {
return this.connectionManager.getAllSessions();
}
/**
* 获取指定客户端会话
*/
getSession(clientId: string): ClientSession | undefined {
return this.connectionManager.getSession(clientId);
}
/**
* 设置事件处理器
*/
override on<K extends keyof NetworkServerEvents>(event: K, handler: NetworkServerEvents[K]): this {
this.eventHandlers[event] = handler;
return this;
}
/**
* 移除事件处理器
*/
override off<K extends keyof NetworkServerEvents>(event: K): this {
delete this.eventHandlers[event];
return this;
}
/**
* 更新配置
*/
updateConfig(newConfig: Partial<NetworkServerConfig>): void {
Object.assign(this.config, newConfig);
this.logger.info('服务器配置已更新:', newConfig);
}
/**
* 设置服务器状态
*/
private setState(newState: ServerState): void {
if (this.state === newState) return;
const oldState = this.state;
this.state = newState;
this.stats.state = newState;
this.logger.info(`服务器状态变化: ${oldState} -> ${newState}`);
}
/**
* 设置事件处理器
*/
private setupEventHandlers(): void {
// 连接管理器事件
this.connectionManager.on('sessionAdded', (session: ClientSession) => {
this.eventHandlers.clientConnected?.(session);
this.updateConnectionPeak();
});
this.connectionManager.on('sessionRemoved', (session: ClientSession, reason?: string) => {
this.eventHandlers.clientDisconnected?.(session, reason);
});
this.connectionManager.on('sessionAuthChanged', (session: ClientSession, authenticated: boolean) => {
if (authenticated) {
this.eventHandlers.clientAuthenticated?.(session);
}
});
// 错误处理器事件
this.errorHandler.on('criticalError', (error: any) => {
this.logger.error('严重错误:', error);
this.eventHandlers.serverError?.(new Error(error.message));
});
}
/**
* 设置传输层事件
*/
private setupTransportEvents(): void {
if (!this.transport) return;
this.transport.onConnect((clientInfo) => {
this.handleClientConnect(clientInfo);
});
this.transport.onDisconnect((clientId, reason) => {
this.handleClientDisconnect(clientId, reason);
});
this.transport.onMessage((clientId, data) => {
this.handleClientMessage(clientId, data);
});
this.transport.onError((error) => {
this.handleTransportError(error);
});
}
/**
* 处理客户端连接
*/
private handleClientConnect(clientInfo: any): void {
try {
// 检查速率限制
if (this.isRateLimited(clientInfo.remoteAddress)) {
this.transport?.disconnectClient(clientInfo.id, '速率限制');
return;
}
// 创建客户端会话
const session = this.connectionManager.addSession(clientInfo);
this.logger.info(`客户端已连接: ${clientInfo.id} from ${clientInfo.remoteAddress}`);
} catch (error) {
this.logger.error('处理客户端连接失败:', error);
this.transport?.disconnectClient(clientInfo.id, '服务器错误');
}
}
/**
* 处理客户端断开连接
*/
private handleClientDisconnect(clientId: string, reason?: string): void {
this.connectionManager.removeSession(clientId, reason);
this.logger.info(`客户端已断开连接: ${clientId}, 原因: ${reason || '未知'}`);
}
/**
* 处理客户端消息
*/
private handleClientMessage(clientId: string, data: ArrayBuffer | string): void {
try {
// 获取客户端会话
const session = this.connectionManager.getSession(clientId);
if (!session) {
this.logger.warn(`收到未知客户端消息: ${clientId}`);
return;
}
// 检查速率限制
if (this.isRateLimited(session.info.remoteAddress)) {
this.kickClient(clientId, '速率限制');
return;
}
// 反序列化消息
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, clientId);
if (!validationResult.isValid) {
this.logger.warn(`消息验证失败: ${validationResult.errors.join(', ')}`);
this.stats.messages.errors++;
return;
}
// 更新心跳
this.connectionManager.updateHeartbeat(clientId);
// 更新统计
this.stats.messages.received++;
this.stats.bandwidth.inbound += (typeof data === 'string' ? data.length : data.byteLength);
// 处理不同类型的消息
this.processMessage(session, message);
this.eventHandlers.messageReceived?.(session, message);
} catch (error) {
this.logger.error(`处理客户端 ${clientId} 消息失败:`, error);
this.stats.messages.errors++;
this.errorHandler.handleError(error as Error, `handleClientMessage:${clientId}`);
}
}
/**
* 处理传输层错误
*/
private handleTransportError(error: Error): void {
this.logger.error('传输层错误:', error);
this.errorHandler.handleError(error, 'transport');
this.eventHandlers.serverError?.(error);
}
/**
* 处理具体消息类型
*/
private processMessage(session: ClientSession, message: INetworkMessage): void {
switch (message.type) {
case MessageType.CONNECT:
this.handleConnectMessage(session, message as IConnectMessage);
break;
case MessageType.HEARTBEAT:
this.handleHeartbeatMessage(session, message as IHeartbeatMessage);
break;
default:
// 其他消息类型由外部处理器处理
break;
}
}
/**
* 处理连接消息
*/
private handleConnectMessage(session: ClientSession, message: IConnectMessage): void {
const response: IConnectResponseMessage = this.messageManager.createMessage(
MessageType.CONNECT,
{
success: true,
clientId: session.id,
serverInfo: {
name: 'ECS Network Server',
version: '1.0.0',
maxPlayers: this.config.transport.maxConnections || 1000,
currentPlayers: this.connectionManager.getAllSessions().length
}
},
'server'
);
this.sendToClient(session.id, response);
if (this.config.authentication.required) {
// 设置认证超时
Core.schedule(this.config.authentication.timeout / 1000, false, this, () => {
if (!session.authenticated) {
this.kickClient(session.id, '认证超时');
}
});
} else {
// 自动设置为已认证
this.connectionManager.setSessionAuthenticated(session.id, true);
}
}
/**
* 处理心跳消息
*/
private handleHeartbeatMessage(session: ClientSession, message: IHeartbeatMessage): void {
const response: IHeartbeatMessage = this.messageManager.createMessage(
MessageType.HEARTBEAT,
{
clientTime: message.data.clientTime,
serverTime: Date.now()
},
'server'
);
this.sendToClient(session.id, response);
}
/**
* 检查速率限制
*/
private isRateLimited(address: string): boolean {
if (!this.config.rateLimit.enabled) {
return false;
}
const now = Date.now();
const limit = this.rateLimitMap.get(address);
if (!limit) {
this.rateLimitMap.set(address, {
count: 1,
resetTime: now + 60000, // 1分钟重置
banned: false
});
return false;
}
// 检查是否被封禁
if (limit.banned && now < limit.resetTime) {
return true;
}
// 重置计数
if (now > limit.resetTime) {
limit.count = 1;
limit.resetTime = now + 60000;
limit.banned = false;
return false;
}
limit.count++;
// 检查是否超过限制
if (limit.count > this.config.rateLimit.maxRequestsPerMinute) {
limit.banned = true;
limit.resetTime = now + this.config.rateLimit.banDuration;
this.logger.warn(`客户端 ${address} 被封禁,原因: 速率限制`);
return true;
}
return false;
}
/**
* 更新连接峰值
*/
private updateConnectionPeak(): void {
const current = this.connectionManager.getAllSessions().length;
if (current > this.stats.connections.peak) {
this.stats.connections.peak = current;
}
}
}

View File

@@ -3,24 +3,16 @@
* ECS Framework网络层 - 服务端实现
*/
// 核心服务器 (待实现)
// export * from './core/NetworkServer';
// export * from './core/ClientConnection';
// 核心服务器
export * from './core/NetworkServer';
export * from './core/ConnectionManager';
// 传输层 (待实现)
// export * from './transport/WebSocketTransport';
// export * from './transport/HttpTransport';
// 传输层
export * from './transport/WebSocketTransport';
// 系统层 (待实现)
// export * from './systems/SyncVarSystem';
// export * from './systems/RpcSystem';
// 房间管理 (待实现)
// export * from './rooms/Room';
// export * from './rooms/RoomManager';
// 认证授权 (待实现)
// export * from './auth/AuthManager';
// 房间管理
export * from './rooms/Room';
export * from './rooms/RoomManager';
// 重新导出shared包的类型
export * from '@esengine/network-shared';

View File

@@ -0,0 +1,507 @@
/**
* 房间基础实现
* 提供房间的基本功能,包括玩家管理和房间状态管理
*/
import { createLogger } from '@esengine/ecs-framework';
import { RoomState, IRoomInfo, INetworkMessage, EventEmitter } from '@esengine/network-shared';
import { ClientSession } from '../core/ConnectionManager';
/**
* 房间配置
*/
export interface RoomConfig {
id: string;
name: string;
maxPlayers: number;
isPublic: boolean;
password?: string;
metadata?: Record<string, any>;
autoDestroy: boolean; // 是否在空房间时自动销毁
customData?: Record<string, any>;
}
/**
* 玩家信息
*/
export interface PlayerInfo {
sessionId: string;
name: string;
isHost: boolean;
joinTime: number;
customData?: Record<string, any>;
}
/**
* 房间事件接口
*/
export interface RoomEvents {
playerJoined: (player: PlayerInfo) => void;
playerLeft: (player: PlayerInfo, reason?: string) => void;
hostChanged: (oldHost: PlayerInfo, newHost: PlayerInfo) => void;
stateChanged: (oldState: RoomState, newState: RoomState) => void;
messageReceived: (message: INetworkMessage, fromPlayer: PlayerInfo) => void;
roomDestroyed: (reason: string) => void;
}
/**
* 房间统计信息
*/
export interface RoomStats {
id: string;
playerCount: number;
maxPlayers: number;
createTime: number;
totalPlayersJoined: number;
messagesSent: number;
messagesReceived: number;
state: RoomState;
}
/**
* 房间类
*/
export class Room extends EventEmitter {
private logger = createLogger('Room');
private config: RoomConfig;
private state: RoomState = RoomState.Waiting;
private players: Map<string, PlayerInfo> = new Map();
private hostId?: string;
private createTime: number = Date.now();
private stats: RoomStats;
// 事件处理器
private eventHandlers: Partial<RoomEvents> = {};
/**
* 构造函数
*/
constructor(config: RoomConfig) {
super();
this.config = { ...config };
this.stats = {
id: config.id,
playerCount: 0,
maxPlayers: config.maxPlayers,
createTime: this.createTime,
totalPlayersJoined: 0,
messagesSent: 0,
messagesReceived: 0,
state: this.state
};
this.logger.info(`房间已创建: ${config.id} (${config.name})`);
}
/**
* 玩家加入房间
*/
addPlayer(session: ClientSession, playerName?: string, password?: string): boolean {
// 检查房间是否已满
if (this.players.size >= this.config.maxPlayers) {
this.logger.warn(`房间已满,拒绝玩家加入: ${session.id}`);
return false;
}
// 检查玩家是否已在房间中
if (this.players.has(session.id)) {
this.logger.warn(`玩家已在房间中: ${session.id}`);
return false;
}
// 检查房间密码
if (this.config.password && password !== this.config.password) {
this.logger.warn(`密码错误,拒绝玩家加入: ${session.id}`);
return false;
}
// 检查房间状态
if (this.state === RoomState.Finished) {
this.logger.warn(`房间已结束,拒绝玩家加入: ${session.id}`);
return false;
}
// 创建玩家信息
const player: PlayerInfo = {
sessionId: session.id,
name: playerName || `Player_${session.id.substr(-6)}`,
isHost: this.players.size === 0, // 第一个加入的玩家成为房主
joinTime: Date.now(),
customData: {}
};
// 添加玩家到房间
this.players.set(session.id, player);
this.stats.playerCount = this.players.size;
this.stats.totalPlayersJoined++;
// 设置房主
if (player.isHost) {
this.hostId = session.id;
}
this.logger.info(`玩家加入房间: ${player.name} (${session.id}) -> 房间 ${this.config.id}`);
// 触发事件
this.eventHandlers.playerJoined?.(player);
this.emit('playerJoined', player);
return true;
}
/**
* 玩家离开房间
*/
removePlayer(sessionId: string, reason?: string): boolean {
const player = this.players.get(sessionId);
if (!player) {
return false;
}
// 从房间移除玩家
this.players.delete(sessionId);
this.stats.playerCount = this.players.size;
this.logger.info(`玩家离开房间: ${player.name} (${sessionId}) <- 房间 ${this.config.id}, 原因: ${reason || '未知'}`);
// 如果离开的是房主,需要转移房主权限
if (player.isHost && this.players.size > 0) {
this.transferHost();
}
// 触发事件
this.eventHandlers.playerLeft?.(player, reason);
this.emit('playerLeft', player, reason);
// 检查是否需要自动销毁房间
if (this.config.autoDestroy && this.players.size === 0) {
this.destroy('房间为空');
}
return true;
}
/**
* 获取玩家信息
*/
getPlayer(sessionId: string): PlayerInfo | undefined {
return this.players.get(sessionId);
}
/**
* 获取所有玩家
*/
getAllPlayers(): PlayerInfo[] {
return Array.from(this.players.values());
}
/**
* 获取房主
*/
getHost(): PlayerInfo | undefined {
return this.hostId ? this.players.get(this.hostId) : undefined;
}
/**
* 转移房主权限
*/
transferHost(newHostId?: string): boolean {
if (this.players.size === 0) {
return false;
}
const oldHost = this.getHost();
let newHost: PlayerInfo | undefined;
if (newHostId) {
newHost = this.players.get(newHostId);
if (!newHost) {
this.logger.warn(`指定的新房主不存在: ${newHostId}`);
return false;
}
} else {
// 自动选择第一个玩家作为新房主
newHost = Array.from(this.players.values())[0];
}
// 更新房主信息
if (oldHost) {
oldHost.isHost = false;
}
newHost.isHost = true;
this.hostId = newHost.sessionId;
this.logger.info(`房主权限转移: ${oldHost?.name || 'unknown'} -> ${newHost.name}`);
// 触发事件
if (oldHost) {
this.eventHandlers.hostChanged?.(oldHost, newHost);
this.emit('hostChanged', oldHost, newHost);
}
return true;
}
/**
* 设置房间状态
*/
setState(newState: RoomState): void {
if (this.state === newState) {
return;
}
const oldState = this.state;
this.state = newState;
this.stats.state = newState;
this.logger.info(`房间状态变化: ${oldState} -> ${newState}`);
// 触发事件
this.eventHandlers.stateChanged?.(oldState, newState);
this.emit('stateChanged', oldState, newState);
}
/**
* 处理房间消息
*/
handleMessage(message: INetworkMessage, fromSessionId: string): void {
const player = this.players.get(fromSessionId);
if (!player) {
this.logger.warn(`收到非房间成员的消息: ${fromSessionId}`);
return;
}
this.stats.messagesReceived++;
// 触发事件
this.eventHandlers.messageReceived?.(message, player);
this.emit('messageReceived', message, player);
}
/**
* 广播消息到房间内所有玩家
*/
broadcast(message: INetworkMessage, exclude?: string[], onSend?: (sessionId: string) => void): void {
const excludeSet = new Set(exclude || []);
let sentCount = 0;
for (const player of this.players.values()) {
if (!excludeSet.has(player.sessionId)) {
if (onSend) {
onSend(player.sessionId);
sentCount++;
}
}
}
this.stats.messagesSent += sentCount;
}
/**
* 检查玩家是否在房间中
*/
hasPlayer(sessionId: string): boolean {
return this.players.has(sessionId);
}
/**
* 检查房间是否已满
*/
isFull(): boolean {
return this.players.size >= this.config.maxPlayers;
}
/**
* 检查房间是否为空
*/
isEmpty(): boolean {
return this.players.size === 0;
}
/**
* 获取房间信息
*/
getRoomInfo(): IRoomInfo {
return {
id: this.config.id,
name: this.config.name,
playerCount: this.players.size,
maxPlayers: this.config.maxPlayers,
state: this.state,
metadata: this.config.metadata
};
}
/**
* 获取房间配置
*/
getConfig(): RoomConfig {
return { ...this.config };
}
/**
* 获取房间统计信息
*/
getStats(): RoomStats {
return {
...this.stats,
playerCount: this.players.size
};
}
/**
* 更新房间配置
*/
updateConfig(updates: Partial<RoomConfig>): void {
Object.assign(this.config, updates);
this.logger.info(`房间配置已更新: ${this.config.id}`, updates);
}
/**
* 设置玩家自定义数据
*/
setPlayerData(sessionId: string, data: Record<string, any>): boolean {
const player = this.players.get(sessionId);
if (!player) {
return false;
}
player.customData = { ...player.customData, ...data };
return true;
}
/**
* 获取房间运行时间
*/
getUptime(): number {
return Date.now() - this.createTime;
}
/**
* 验证密码
*/
validatePassword(password?: string): boolean {
if (!this.config.password) {
return true; // 无密码房间
}
return password === this.config.password;
}
/**
* 设置事件处理器
*/
override on<K extends keyof RoomEvents>(event: K, handler: RoomEvents[K]): this {
this.eventHandlers[event] = handler;
return super.on(event, handler as any);
}
/**
* 移除事件处理器
*/
override off<K extends keyof RoomEvents>(event: K): this {
delete this.eventHandlers[event];
return super.off(event, this.eventHandlers[event] as any);
}
/**
* 销毁房间
*/
destroy(reason: string = '房间关闭'): void {
this.logger.info(`房间销毁: ${this.config.id}, 原因: ${reason}`);
// 清理所有玩家
const playersToRemove = Array.from(this.players.keys());
for (const sessionId of playersToRemove) {
this.removePlayer(sessionId, reason);
}
// 触发销毁事件
this.eventHandlers.roomDestroyed?.(reason);
this.emit('roomDestroyed', reason);
// 清理资源
this.players.clear();
this.removeAllListeners();
}
/**
* 获取房间详细状态
*/
getDetailedStatus() {
return {
config: this.getConfig(),
info: this.getRoomInfo(),
stats: this.getStats(),
players: this.getAllPlayers(),
host: this.getHost(),
uptime: this.getUptime(),
isEmpty: this.isEmpty(),
isFull: this.isFull()
};
}
/**
* 踢出玩家
*/
kickPlayer(sessionId: string, reason: string = '被踢出房间'): boolean {
if (!this.hasPlayer(sessionId)) {
return false;
}
return this.removePlayer(sessionId, reason);
}
/**
* 暂停房间
*/
pause(): void {
if (this.state === RoomState.Playing) {
this.setState(RoomState.Paused);
}
}
/**
* 恢复房间
*/
resume(): void {
if (this.state === RoomState.Paused) {
this.setState(RoomState.Playing);
}
}
/**
* 开始游戏
*/
startGame(): boolean {
if (this.state !== RoomState.Waiting) {
return false;
}
if (this.players.size === 0) {
return false;
}
this.setState(RoomState.Playing);
return true;
}
/**
* 结束游戏
*/
endGame(): boolean {
if (this.state !== RoomState.Playing && this.state !== RoomState.Paused) {
return false;
}
this.setState(RoomState.Finished);
return true;
}
/**
* 重置房间到等待状态
*/
reset(): void {
this.setState(RoomState.Waiting);
// 可以根据需要重置其他状态
}
}

View File

@@ -0,0 +1,621 @@
/**
* 房间管理器
* 负责房间的创建、销毁和管理
*/
import { createLogger, ITimer, Core } from '@esengine/ecs-framework';
import { Room, RoomConfig, PlayerInfo, RoomEvents } from './Room';
import { ClientSession } from '../core/ConnectionManager';
import { RoomState, IRoomInfo, EventEmitter } from '@esengine/network-shared';
/**
* 房间管理器配置
*/
export interface RoomManagerConfig {
maxRooms: number;
defaultMaxPlayers: number;
autoCleanupInterval: number; // 自动清理间隔(毫秒)
roomIdLength: number;
allowDuplicateNames: boolean;
defaultAutoDestroy: boolean;
}
/**
* 房间查询选项
*/
export interface RoomQueryOptions {
state?: RoomState;
hasPassword?: boolean;
minPlayers?: number;
maxPlayers?: number;
notFull?: boolean;
publicOnly?: boolean;
limit?: number;
offset?: number;
}
/**
* 房间创建选项
*/
export interface CreateRoomOptions {
id?: string;
name: string;
maxPlayers?: number;
isPublic?: boolean;
password?: string;
metadata?: Record<string, any>;
autoDestroy?: boolean;
}
/**
* 房间管理器事件接口
*/
export interface RoomManagerEvents {
roomCreated: (room: Room) => void;
roomDestroyed: (room: Room, reason: string) => void;
playerJoinedRoom: (room: Room, player: PlayerInfo) => void;
playerLeftRoom: (room: Room, player: PlayerInfo, reason?: string) => void;
}
/**
* 房间管理器统计
*/
export interface RoomManagerStats {
totalRooms: number;
activeRooms: number;
totalPlayers: number;
roomsByState: Record<RoomState, number>;
roomsCreated: number;
roomsDestroyed: number;
playersJoined: number;
playersLeft: number;
}
/**
* 房间管理器
*/
export class RoomManager extends EventEmitter {
private logger = createLogger('RoomManager');
private config: RoomManagerConfig;
private rooms: Map<string, Room> = new Map();
private playerRoomMap: Map<string, string> = new Map(); // sessionId -> roomId
private stats: RoomManagerStats;
private cleanupTimer?: ITimer;
// 事件处理器
private eventHandlers: Partial<RoomManagerEvents> = {};
/**
* 构造函数
*/
constructor(config: Partial<RoomManagerConfig> = {}) {
super();
this.config = {
maxRooms: 1000,
defaultMaxPlayers: 8,
autoCleanupInterval: 300000, // 5分钟
roomIdLength: 8,
allowDuplicateNames: true,
defaultAutoDestroy: true,
...config
};
this.stats = {
totalRooms: 0,
activeRooms: 0,
totalPlayers: 0,
roomsByState: {
[RoomState.Waiting]: 0,
[RoomState.Playing]: 0,
[RoomState.Paused]: 0,
[RoomState.Finished]: 0
},
roomsCreated: 0,
roomsDestroyed: 0,
playersJoined: 0,
playersLeft: 0
};
this.startAutoCleanup();
}
/**
* 创建房间
*/
createRoom(creatorSession: ClientSession, options: CreateRoomOptions): Room | null {
// 检查房间数量限制
if (this.rooms.size >= this.config.maxRooms) {
this.logger.warn(`房间数量已达上限: ${this.config.maxRooms}`);
return null;
}
// 检查玩家是否已在其他房间
if (this.playerRoomMap.has(creatorSession.id)) {
this.logger.warn(`玩家已在其他房间中: ${creatorSession.id}`);
return null;
}
// 检查房间名称重复
if (!this.config.allowDuplicateNames && this.isNameExists(options.name)) {
this.logger.warn(`房间名称已存在: ${options.name}`);
return null;
}
// 生成房间ID
const roomId = options.id || this.generateRoomId();
if (this.rooms.has(roomId)) {
this.logger.warn(`房间ID已存在: ${roomId}`);
return null;
}
// 创建房间配置
const roomConfig: RoomConfig = {
id: roomId,
name: options.name,
maxPlayers: options.maxPlayers || this.config.defaultMaxPlayers,
isPublic: options.isPublic !== false, // 默认为公开
password: options.password,
metadata: options.metadata || {},
autoDestroy: options.autoDestroy ?? this.config.defaultAutoDestroy
};
try {
// 创建房间实例
const room = new Room(roomConfig);
this.setupRoomEvents(room);
// 添加到房间列表
this.rooms.set(roomId, room);
// 创建者自动加入房间
const success = room.addPlayer(creatorSession, `Creator_${creatorSession.id.substr(-6)}`);
if (!success) {
// 加入失败,销毁房间
this.destroyRoom(roomId, '创建者加入失败');
return null;
}
// 更新玩家房间映射
this.playerRoomMap.set(creatorSession.id, roomId);
// 更新统计
this.stats.roomsCreated++;
this.updateStats();
this.logger.info(`房间创建成功: ${roomId} by ${creatorSession.id}`);
// 触发事件
this.eventHandlers.roomCreated?.(room);
this.emit('roomCreated', room);
return room;
} catch (error) {
this.logger.error(`创建房间失败: ${roomId}`, error);
return null;
}
}
/**
* 销毁房间
*/
destroyRoom(roomId: string, reason: string = '房间关闭'): boolean {
const room = this.rooms.get(roomId);
if (!room) {
return false;
}
// 移除所有玩家的房间映射
for (const player of room.getAllPlayers()) {
this.playerRoomMap.delete(player.sessionId);
}
// 销毁房间
room.destroy(reason);
// 从房间列表移除
this.rooms.delete(roomId);
// 更新统计
this.stats.roomsDestroyed++;
this.updateStats();
this.logger.info(`房间已销毁: ${roomId}, 原因: ${reason}`);
// 触发事件
this.eventHandlers.roomDestroyed?.(room, reason);
this.emit('roomDestroyed', room, reason);
return true;
}
/**
* 玩家加入房间
*/
joinRoom(session: ClientSession, roomId: string, password?: string, playerName?: string): boolean {
// 检查玩家是否已在其他房间
if (this.playerRoomMap.has(session.id)) {
this.logger.warn(`玩家已在其他房间中: ${session.id}`);
return false;
}
// 获取房间
const room = this.rooms.get(roomId);
if (!room) {
this.logger.warn(`房间不存在: ${roomId}`);
return false;
}
// 尝试加入房间
const success = room.addPlayer(session, playerName, password);
if (!success) {
return false;
}
// 更新玩家房间映射
this.playerRoomMap.set(session.id, roomId);
// 更新统计
this.stats.playersJoined++;
this.updateStats();
const player = room.getPlayer(session.id)!;
this.eventHandlers.playerJoinedRoom?.(room, player);
this.emit('playerJoinedRoom', room, player);
return true;
}
/**
* 玩家离开房间
*/
leaveRoom(sessionId: string, reason?: string): boolean {
const roomId = this.playerRoomMap.get(sessionId);
if (!roomId) {
return false;
}
const room = this.rooms.get(roomId);
if (!room) {
this.playerRoomMap.delete(sessionId);
return false;
}
const player = room.getPlayer(sessionId);
if (!player) {
this.playerRoomMap.delete(sessionId);
return false;
}
// 从房间移除玩家
const success = room.removePlayer(sessionId, reason);
if (success) {
// 更新玩家房间映射
this.playerRoomMap.delete(sessionId);
// 更新统计
this.stats.playersLeft++;
this.updateStats();
this.eventHandlers.playerLeftRoom?.(room, player, reason);
this.emit('playerLeftRoom', room, player, reason);
}
return success;
}
/**
* 获取房间
*/
getRoom(roomId: string): Room | undefined {
return this.rooms.get(roomId);
}
/**
* 获取玩家所在房间
*/
getPlayerRoom(sessionId: string): Room | undefined {
const roomId = this.playerRoomMap.get(sessionId);
return roomId ? this.rooms.get(roomId) : undefined;
}
/**
* 查询房间列表
*/
queryRooms(options: RoomQueryOptions = {}): Room[] {
let rooms = Array.from(this.rooms.values());
// 应用过滤条件
if (options.state !== undefined) {
rooms = rooms.filter(room => room.getRoomInfo().state === options.state);
}
if (options.hasPassword !== undefined) {
rooms = rooms.filter(room => {
const config = room.getConfig();
return options.hasPassword ? !!config.password : !config.password;
});
}
if (options.minPlayers !== undefined) {
rooms = rooms.filter(room => room.getAllPlayers().length >= options.minPlayers!);
}
if (options.maxPlayers !== undefined) {
rooms = rooms.filter(room => room.getAllPlayers().length <= options.maxPlayers!);
}
if (options.notFull) {
rooms = rooms.filter(room => !room.isFull());
}
if (options.publicOnly) {
rooms = rooms.filter(room => room.getConfig().isPublic);
}
// 分页
if (options.offset) {
rooms = rooms.slice(options.offset);
}
if (options.limit) {
rooms = rooms.slice(0, options.limit);
}
return rooms;
}
/**
* 获取房间信息列表
*/
getRoomInfoList(options: RoomQueryOptions = {}): IRoomInfo[] {
return this.queryRooms(options).map(room => room.getRoomInfo());
}
/**
* 获取统计信息
*/
getStats(): RoomManagerStats {
this.updateStats();
return { ...this.stats };
}
/**
* 重置统计信息
*/
resetStats(): void {
this.stats = {
totalRooms: this.rooms.size,
activeRooms: this.rooms.size,
totalPlayers: this.playerRoomMap.size,
roomsByState: {
[RoomState.Waiting]: 0,
[RoomState.Playing]: 0,
[RoomState.Paused]: 0,
[RoomState.Finished]: 0
},
roomsCreated: 0,
roomsDestroyed: 0,
playersJoined: 0,
playersLeft: 0
};
this.updateStats();
}
/**
* 更新配置
*/
updateConfig(newConfig: Partial<RoomManagerConfig>): void {
Object.assign(this.config, newConfig);
this.logger.info('房间管理器配置已更新:', newConfig);
}
/**
* 设置事件处理器
*/
override on<K extends keyof RoomManagerEvents>(event: K, handler: RoomManagerEvents[K]): this {
this.eventHandlers[event] = handler;
return super.on(event, handler as any);
}
/**
* 移除事件处理器
*/
override off<K extends keyof RoomManagerEvents>(event: K): this {
delete this.eventHandlers[event];
return super.off(event, this.eventHandlers[event] as any);
}
/**
* 销毁管理器
*/
destroy(): void {
// 停止自动清理
if (this.cleanupTimer) {
this.cleanupTimer.stop();
this.cleanupTimer = undefined;
}
// 销毁所有房间
const roomIds = Array.from(this.rooms.keys());
for (const roomId of roomIds) {
this.destroyRoom(roomId, '管理器销毁');
}
// 清理资源
this.rooms.clear();
this.playerRoomMap.clear();
this.removeAllListeners();
}
/**
* 生成房间ID
*/
private generateRoomId(): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let result = '';
for (let i = 0; i < this.config.roomIdLength; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
// 确保ID唯一
if (this.rooms.has(result)) {
return this.generateRoomId();
}
return result;
}
/**
* 检查房间名称是否存在
*/
private isNameExists(name: string): boolean {
for (const room of this.rooms.values()) {
if (room.getConfig().name === name) {
return true;
}
}
return false;
}
/**
* 设置房间事件监听
*/
private setupRoomEvents(room: Room): void {
room.on('roomDestroyed', (reason) => {
// 自动清理已销毁的房间
this.rooms.delete(room.getConfig().id);
this.updateStats();
});
}
/**
* 更新统计信息
*/
private updateStats(): void {
this.stats.totalRooms = this.rooms.size;
this.stats.activeRooms = this.rooms.size;
this.stats.totalPlayers = this.playerRoomMap.size;
// 重置状态统计
this.stats.roomsByState = {
[RoomState.Waiting]: 0,
[RoomState.Playing]: 0,
[RoomState.Paused]: 0,
[RoomState.Finished]: 0
};
// 统计各状态房间数量
for (const room of this.rooms.values()) {
const state = room.getRoomInfo().state;
this.stats.roomsByState[state]++;
}
}
/**
* 启动自动清理
*/
private startAutoCleanup(): void {
this.cleanupTimer = Core.schedule(this.config.autoCleanupInterval / 1000, true, this, () => {
this.performAutoCleanup();
});
}
/**
* 执行自动清理
*/
private performAutoCleanup(): void {
const now = Date.now();
const roomsToDestroy: string[] = [];
for (const [roomId, room] of this.rooms) {
const config = room.getConfig();
const stats = room.getStats();
// 清理空房间(如果启用了自动销毁)
if (config.autoDestroy && room.isEmpty()) {
roomsToDestroy.push(roomId);
continue;
}
// 清理长时间无活动的已结束房间
if (stats.state === RoomState.Finished &&
now - stats.createTime > 3600000) { // 1小时
roomsToDestroy.push(roomId);
continue;
}
}
// 执行清理
for (const roomId of roomsToDestroy) {
this.destroyRoom(roomId, '自动清理');
}
if (roomsToDestroy.length > 0) {
this.logger.info(`自动清理了 ${roomsToDestroy.length} 个房间`);
}
}
/**
* 获取管理器状态摘要
*/
getStatusSummary() {
const stats = this.getStats();
const rooms = Array.from(this.rooms.values());
return {
stats,
roomCount: rooms.length,
playerCount: this.playerRoomMap.size,
publicRooms: rooms.filter(r => r.getConfig().isPublic).length,
privateRooms: rooms.filter(r => !r.getConfig().isPublic).length,
fullRooms: rooms.filter(r => r.isFull()).length,
emptyRooms: rooms.filter(r => r.isEmpty()).length,
averagePlayersPerRoom: rooms.length > 0 ?
rooms.reduce((sum, r) => sum + r.getAllPlayers().length, 0) / rooms.length : 0
};
}
/**
* 踢出玩家(从其所在房间)
*/
kickPlayer(sessionId: string, reason: string = '被管理员踢出'): boolean {
const room = this.getPlayerRoom(sessionId);
if (!room) {
return false;
}
return room.kickPlayer(sessionId, reason);
}
/**
* 批量销毁房间
*/
destroyRoomsBatch(roomIds: string[], reason: string = '批量清理'): number {
let destroyedCount = 0;
for (const roomId of roomIds) {
if (this.destroyRoom(roomId, reason)) {
destroyedCount++;
}
}
return destroyedCount;
}
/**
* 检查玩家是否在房间中
*/
isPlayerInRoom(sessionId: string): boolean {
return this.playerRoomMap.has(sessionId);
}
/**
* 获取玩家所在房间ID
*/
getPlayerRoomId(sessionId: string): string | undefined {
return this.playerRoomMap.get(sessionId);
}
}

View File

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