更新network库及core库优化

This commit is contained in:
YHH
2025-08-12 09:39:07 +08:00
parent c178e2fbcc
commit 9f76d37a82
117 changed files with 17988 additions and 4099 deletions

View File

@@ -0,0 +1,478 @@
/**
* 客户端连接管理
*/
import { EventEmitter } from 'events';
import { NetworkValue, NetworkMessage } from '@esengine/ecs-framework-network-shared';
import { TransportMessage } from './Transport';
/**
* 客户端连接状态
*/
export enum ClientConnectionState {
/** 连接中 */
CONNECTING = 'connecting',
/** 已连接 */
CONNECTED = 'connected',
/** 认证中 */
AUTHENTICATING = 'authenticating',
/** 已认证 */
AUTHENTICATED = 'authenticated',
/** 断开连接中 */
DISCONNECTING = 'disconnecting',
/** 已断开 */
DISCONNECTED = 'disconnected',
/** 错误状态 */
ERROR = 'error'
}
/**
* 客户端权限
*/
export interface ClientPermissions {
/** 是否可以加入房间 */
canJoinRooms?: boolean;
/** 是否可以创建房间 */
canCreateRooms?: boolean;
/** 是否可以发送RPC */
canSendRpc?: boolean;
/** 是否可以同步变量 */
canSyncVars?: boolean;
/** 自定义权限 */
customPermissions?: Record<string, boolean>;
}
/**
* 客户端连接事件
*/
export interface ClientConnectionEvents {
/** 状态变化 */
'state-changed': (oldState: ClientConnectionState, newState: ClientConnectionState) => void;
/** 收到消息 */
'message': (message: TransportMessage) => void;
/** 连接错误 */
'error': (error: Error) => void;
/** 连接超时 */
'timeout': () => void;
/** 身份验证成功 */
'authenticated': (userData: Record<string, NetworkValue>) => void;
/** 身份验证失败 */
'authentication-failed': (reason: string) => void;
}
/**
* 客户端统计信息
*/
export interface ClientStats {
/** 消息发送数 */
messagesSent: number;
/** 消息接收数 */
messagesReceived: number;
/** 字节发送数 */
bytesSent: number;
/** 字节接收数 */
bytesReceived: number;
/** 最后活跃时间 */
lastActivity: Date;
/** 连接时长(毫秒) */
connectionDuration: number;
}
/**
* 客户端连接管理类
*/
export class ClientConnection extends EventEmitter {
/** 连接ID */
public readonly id: string;
/** 客户端IP地址 */
public readonly remoteAddress: string;
/** 连接创建时间 */
public readonly connectedAt: Date;
/** 当前状态 */
private _state: ClientConnectionState = ClientConnectionState.CONNECTING;
/** 用户数据 */
private _userData: Record<string, NetworkValue> = {};
/** 权限信息 */
private _permissions: ClientPermissions = {};
/** 所在房间ID */
private _currentRoomId: string | null = null;
/** 统计信息 */
private _stats: ClientStats;
/** 最后活跃时间 */
private _lastActivity: Date;
/** 超时定时器 */
private _timeoutTimer: NodeJS.Timeout | null = null;
/** 连接超时时间(毫秒) */
private _connectionTimeout: number;
/** 发送消息回调 */
private _sendMessageCallback: (message: TransportMessage) => Promise<boolean>;
constructor(
id: string,
remoteAddress: string,
sendMessageCallback: (message: TransportMessage) => Promise<boolean>,
options: {
connectionTimeout?: number;
userData?: Record<string, NetworkValue>;
permissions?: ClientPermissions;
} = {}
) {
super();
this.id = id;
this.remoteAddress = remoteAddress;
this.connectedAt = new Date();
this._lastActivity = new Date();
this._connectionTimeout = options.connectionTimeout || 60000; // 1分钟
this._sendMessageCallback = sendMessageCallback;
if (options.userData) {
this._userData = { ...options.userData };
}
if (options.permissions) {
this._permissions = { ...options.permissions };
}
this._stats = {
messagesSent: 0,
messagesReceived: 0,
bytesSent: 0,
bytesReceived: 0,
lastActivity: this._lastActivity,
connectionDuration: 0
};
this.setState(ClientConnectionState.CONNECTED);
this.startTimeout();
}
/**
* 获取当前状态
*/
get state(): ClientConnectionState {
return this._state;
}
/**
* 获取用户数据
*/
get userData(): Readonly<Record<string, NetworkValue>> {
return this._userData;
}
/**
* 获取权限信息
*/
get permissions(): Readonly<ClientPermissions> {
return this._permissions;
}
/**
* 获取当前房间ID
*/
get currentRoomId(): string | null {
return this._currentRoomId;
}
/**
* 获取统计信息
*/
get stats(): Readonly<ClientStats> {
this._stats.connectionDuration = Date.now() - this.connectedAt.getTime();
this._stats.lastActivity = this._lastActivity;
return this._stats;
}
/**
* 获取最后活跃时间
*/
get lastActivity(): Date {
return this._lastActivity;
}
/**
* 是否已连接
*/
get isConnected(): boolean {
return this._state === ClientConnectionState.CONNECTED ||
this._state === ClientConnectionState.AUTHENTICATED;
}
/**
* 是否已认证
*/
get isAuthenticated(): boolean {
return this._state === ClientConnectionState.AUTHENTICATED;
}
/**
* 发送消息
*/
async sendMessage(message: TransportMessage): Promise<boolean> {
if (!this.isConnected) {
return false;
}
try {
const success = await this._sendMessageCallback(message);
if (success) {
this._stats.messagesSent++;
const messageSize = JSON.stringify(message).length;
this._stats.bytesSent += messageSize;
this.updateActivity();
}
return success;
} catch (error) {
this.handleError(error as Error);
return false;
}
}
/**
* 处理接收到的消息
*/
handleMessage(message: TransportMessage): void {
if (!this.isConnected) {
return;
}
this._stats.messagesReceived++;
const messageSize = JSON.stringify(message).length;
this._stats.bytesReceived += messageSize;
this.updateActivity();
this.emit('message', message);
}
/**
* 设置用户数据
*/
setUserData(key: string, value: NetworkValue): void {
this._userData[key] = value;
}
/**
* 获取用户数据
*/
getUserData<T extends NetworkValue = NetworkValue>(key: string): T | undefined {
return this._userData[key] as T;
}
/**
* 批量设置用户数据
*/
setUserDataBatch(data: Record<string, NetworkValue>): void {
Object.assign(this._userData, data);
}
/**
* 设置权限
*/
setPermission(permission: keyof ClientPermissions, value: boolean): void {
(this._permissions as any)[permission] = value;
}
/**
* 检查权限
*/
hasPermission(permission: keyof ClientPermissions): boolean {
return (this._permissions as any)[permission] || false;
}
/**
* 设置自定义权限
*/
setCustomPermission(permission: string, value: boolean): void {
if (!this._permissions.customPermissions) {
this._permissions.customPermissions = {};
}
this._permissions.customPermissions[permission] = value;
}
/**
* 检查自定义权限
*/
hasCustomPermission(permission: string): boolean {
return this._permissions.customPermissions?.[permission] || false;
}
/**
* 进行身份认证
*/
async authenticate(credentials: Record<string, NetworkValue>): Promise<boolean> {
if (this._state !== ClientConnectionState.CONNECTED) {
return false;
}
this.setState(ClientConnectionState.AUTHENTICATING);
try {
// 这里可以添加实际的认证逻辑
// 目前简单地认为所有认证都成功
this.setUserDataBatch(credentials);
this.setState(ClientConnectionState.AUTHENTICATED);
this.emit('authenticated', credentials);
return true;
} catch (error) {
this.setState(ClientConnectionState.CONNECTED);
this.emit('authentication-failed', (error as Error).message);
return false;
}
}
/**
* 加入房间
*/
joinRoom(roomId: string): void {
this._currentRoomId = roomId;
}
/**
* 离开房间
*/
leaveRoom(): void {
this._currentRoomId = null;
}
/**
* 断开连接
*/
disconnect(reason?: string): void {
if (this._state === ClientConnectionState.DISCONNECTED) {
return;
}
this.setState(ClientConnectionState.DISCONNECTING);
this.stopTimeout();
// 发送断开连接消息
this.sendMessage({
type: 'system',
data: {
action: 'disconnect',
reason: reason || 'server-disconnect'
}
}).finally(() => {
this.setState(ClientConnectionState.DISCONNECTED);
});
}
/**
* 更新活跃时间
*/
updateActivity(): void {
this._lastActivity = new Date();
this.resetTimeout();
}
/**
* 设置连接状态
*/
private setState(newState: ClientConnectionState): void {
const oldState = this._state;
if (oldState !== newState) {
this._state = newState;
this.emit('state-changed', oldState, newState);
}
}
/**
* 处理错误
*/
private handleError(error: Error): void {
this.setState(ClientConnectionState.ERROR);
this.emit('error', error);
}
/**
* 启动超时检测
*/
private startTimeout(): void {
this.resetTimeout();
}
/**
* 重置超时定时器
*/
private resetTimeout(): void {
this.stopTimeout();
if (this._connectionTimeout > 0) {
this._timeoutTimer = setTimeout(() => {
this.handleTimeout();
}, this._connectionTimeout);
}
}
/**
* 停止超时检测
*/
private stopTimeout(): void {
if (this._timeoutTimer) {
clearTimeout(this._timeoutTimer);
this._timeoutTimer = null;
}
}
/**
* 处理超时
*/
private handleTimeout(): void {
this.emit('timeout');
this.disconnect('timeout');
}
/**
* 销毁连接
*/
destroy(): void {
this.stopTimeout();
this.removeAllListeners();
this.setState(ClientConnectionState.DISCONNECTED);
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof ClientConnectionEvents>(event: K, listener: ClientConnectionEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof ClientConnectionEvents>(event: K, ...args: Parameters<ClientConnectionEvents[K]>): boolean {
return super.emit(event, ...args);
}
/**
* 序列化连接信息
*/
toJSON(): object {
return {
id: this.id,
remoteAddress: this.remoteAddress,
state: this._state,
connectedAt: this.connectedAt.toISOString(),
lastActivity: this._lastActivity.toISOString(),
currentRoomId: this._currentRoomId,
userData: this._userData,
permissions: this._permissions,
stats: this.stats
};
}
}

View File

@@ -0,0 +1,602 @@
/**
* HTTP 传输层实现
*
* 用于处理 REST API 请求和长轮询连接
*/
import { createServer, IncomingMessage, ServerResponse, Server as HttpServer } from 'http';
import { parse as parseUrl } from 'url';
import { v4 as uuidv4 } from 'uuid';
import { Transport, TransportConfig, ClientConnectionInfo, TransportMessage } from './Transport';
/**
* HTTP 传输配置
*/
export interface HttpTransportConfig extends TransportConfig {
/** API 路径前缀 */
apiPrefix?: string;
/** 最大请求大小(字节) */
maxRequestSize?: number;
/** 长轮询超时(毫秒) */
longPollTimeout?: number;
/** 是否启用 CORS */
enableCors?: boolean;
/** 允许的域名 */
corsOrigins?: string[];
}
/**
* HTTP 请求上下文
*/
interface HttpRequestContext {
/** 请求ID */
id: string;
/** HTTP 请求 */
request: IncomingMessage;
/** HTTP 响应 */
response: ServerResponse;
/** 解析后的URL */
parsedUrl: any;
/** 请求体数据 */
body?: string;
/** 查询参数 */
query: Record<string, string>;
}
/**
* HTTP 客户端连接信息(用于长轮询)
*/
interface HttpConnectionInfo extends ClientConnectionInfo {
/** 长轮询响应对象 */
longPollResponse?: ServerResponse;
/** 消息队列 */
messageQueue: TransportMessage[];
/** 长轮询超时定时器 */
longPollTimer?: NodeJS.Timeout;
}
/**
* HTTP 传输层实现
*/
export class HttpTransport extends Transport {
private httpServer: HttpServer | null = null;
private httpConnections = new Map<string, HttpConnectionInfo>();
protected override config: HttpTransportConfig;
constructor(config: HttpTransportConfig) {
super(config);
this.config = {
apiPrefix: '/api',
maxRequestSize: 1024 * 1024, // 1MB
longPollTimeout: 30000, // 30秒
enableCors: true,
corsOrigins: ['*'],
heartbeatInterval: 60000,
connectionTimeout: 120000,
maxConnections: 1000,
...config
};
}
/**
* 启动 HTTP 服务器
*/
async start(): Promise<void> {
if (this.isRunning) {
throw new Error('HTTP transport is already running');
}
try {
this.httpServer = createServer((req, res) => {
this.handleHttpRequest(req, res);
});
this.httpServer.on('error', (error: Error) => {
this.handleError(error);
});
await new Promise<void>((resolve, reject) => {
this.httpServer!.listen(this.config.port, this.config.host, (error?: Error) => {
if (error) {
reject(error);
} else {
this.isRunning = true;
resolve();
}
});
});
this.emit('server-started', this.config);
} catch (error) {
await this.cleanup();
throw error;
}
}
/**
* 停止 HTTP 服务器
*/
async stop(): Promise<void> {
if (!this.isRunning) {
return;
}
this.isRunning = false;
// 断开所有长轮询连接
for (const [connectionId] of this.httpConnections) {
this.disconnectClient(connectionId, 'server-shutdown');
}
await this.cleanup();
this.emit('server-stopped');
}
/**
* 发送消息给指定客户端
*/
async sendToClient(connectionId: string, message: TransportMessage): Promise<boolean> {
const connection = this.httpConnections.get(connectionId);
if (!connection) {
return false;
}
// 如果有长轮询连接,直接发送
if (connection.longPollResponse && !connection.longPollResponse.headersSent) {
this.sendLongPollResponse(connection, [message]);
return true;
}
// 否则加入消息队列
connection.messageQueue.push(message);
return true;
}
/**
* 广播消息给所有客户端
*/
async broadcast(message: TransportMessage, excludeId?: string): Promise<number> {
let sentCount = 0;
for (const [connectionId, connection] of this.httpConnections) {
if (excludeId && connectionId === excludeId) {
continue;
}
if (await this.sendToClient(connectionId, message)) {
sentCount++;
}
}
return sentCount;
}
/**
* 发送消息给指定客户端列表
*/
async sendToClients(connectionIds: string[], message: TransportMessage): Promise<number> {
let sentCount = 0;
for (const connectionId of connectionIds) {
if (await this.sendToClient(connectionId, message)) {
sentCount++;
}
}
return sentCount;
}
/**
* 断开指定客户端连接
*/
async disconnectClient(connectionId: string, reason?: string): Promise<void> {
const connection = this.httpConnections.get(connectionId);
if (connection) {
this.cleanupConnection(connectionId);
this.removeConnection(connectionId, reason);
}
}
/**
* 处理 HTTP 请求
*/
private async handleHttpRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
try {
// 设置 CORS 头
if (this.config.enableCors) {
this.setCorsHeaders(res);
}
// 处理 OPTIONS 请求
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
const parsedUrl = parseUrl(req.url || '', true);
const pathname = parsedUrl.pathname || '';
// 检查是否为 API 请求
if (!pathname.startsWith(this.config.apiPrefix!)) {
this.sendErrorResponse(res, 404, 'Not Found');
return;
}
const context: HttpRequestContext = {
id: uuidv4(),
request: req,
response: res,
parsedUrl,
query: parsedUrl.query as Record<string, string>,
};
// 读取请求体
if (req.method === 'POST' || req.method === 'PUT') {
context.body = await this.readRequestBody(req);
}
// 路由处理
const apiPath = pathname.substring(this.config.apiPrefix!.length);
await this.routeApiRequest(context, apiPath);
} catch (error) {
this.handleError(error as Error);
this.sendErrorResponse(res, 500, 'Internal Server Error');
}
}
/**
* API 路由处理
*/
private async routeApiRequest(context: HttpRequestContext, apiPath: string): Promise<void> {
const { request, response } = context;
switch (apiPath) {
case '/connect':
if (request.method === 'POST') {
await this.handleConnect(context);
} else {
this.sendErrorResponse(response, 405, 'Method Not Allowed');
}
break;
case '/disconnect':
if (request.method === 'POST') {
await this.handleDisconnect(context);
} else {
this.sendErrorResponse(response, 405, 'Method Not Allowed');
}
break;
case '/poll':
if (request.method === 'GET') {
await this.handleLongPoll(context);
} else {
this.sendErrorResponse(response, 405, 'Method Not Allowed');
}
break;
case '/send':
if (request.method === 'POST') {
await this.handleSendMessage(context);
} else {
this.sendErrorResponse(response, 405, 'Method Not Allowed');
}
break;
case '/status':
if (request.method === 'GET') {
await this.handleStatus(context);
} else {
this.sendErrorResponse(response, 405, 'Method Not Allowed');
}
break;
default:
this.sendErrorResponse(response, 404, 'API endpoint not found');
break;
}
}
/**
* 处理连接请求
*/
private async handleConnect(context: HttpRequestContext): Promise<void> {
const { request, response } = context;
try {
// 检查连接数限制
if (this.config.maxConnections && this.httpConnections.size >= this.config.maxConnections) {
this.sendErrorResponse(response, 429, 'Too many connections');
return;
}
const connectionId = uuidv4();
const remoteAddress = request.socket.remoteAddress || request.headers['x-forwarded-for'] || 'unknown';
const connectionInfo: HttpConnectionInfo = {
id: connectionId,
remoteAddress: Array.isArray(remoteAddress) ? remoteAddress[0] : remoteAddress,
connectedAt: new Date(),
lastActivity: new Date(),
userData: {},
messageQueue: []
};
this.httpConnections.set(connectionId, connectionInfo);
this.addConnection(connectionInfo);
this.sendJsonResponse(response, 200, {
success: true,
connectionId,
serverTime: Date.now()
});
} catch (error) {
this.handleError(error as Error);
this.sendErrorResponse(response, 500, 'Failed to create connection');
}
}
/**
* 处理断开连接请求
*/
private async handleDisconnect(context: HttpRequestContext): Promise<void> {
const { response, query } = context;
const connectionId = query.connectionId;
if (!connectionId) {
this.sendErrorResponse(response, 400, 'Missing connectionId');
return;
}
await this.disconnectClient(connectionId, 'client-disconnect');
this.sendJsonResponse(response, 200, {
success: true,
message: 'Disconnected successfully'
});
}
/**
* 处理长轮询请求
*/
private async handleLongPoll(context: HttpRequestContext): Promise<void> {
const { response, query } = context;
const connectionId = query.connectionId;
if (!connectionId) {
this.sendErrorResponse(response, 400, 'Missing connectionId');
return;
}
const connection = this.httpConnections.get(connectionId);
if (!connection) {
this.sendErrorResponse(response, 404, 'Connection not found');
return;
}
this.updateClientActivity(connectionId);
// 如果有排队的消息,立即返回
if (connection.messageQueue.length > 0) {
const messages = connection.messageQueue.splice(0);
this.sendLongPollResponse(connection, messages);
return;
}
// 设置长轮询
connection.longPollResponse = response;
// 设置超时
connection.longPollTimer = setTimeout(() => {
this.sendLongPollResponse(connection, []);
}, this.config.longPollTimeout);
}
/**
* 处理发送消息请求
*/
private async handleSendMessage(context: HttpRequestContext): Promise<void> {
const { response, query, body } = context;
const connectionId = query.connectionId;
if (!connectionId) {
this.sendErrorResponse(response, 400, 'Missing connectionId');
return;
}
const connection = this.httpConnections.get(connectionId);
if (!connection) {
this.sendErrorResponse(response, 404, 'Connection not found');
return;
}
if (!body) {
this.sendErrorResponse(response, 400, 'Missing message body');
return;
}
try {
const message = JSON.parse(body) as TransportMessage;
message.senderId = connectionId;
this.handleMessage(connectionId, message);
this.sendJsonResponse(response, 200, {
success: true,
message: 'Message sent successfully'
});
} catch (error) {
this.sendErrorResponse(response, 400, 'Invalid message format');
}
}
/**
* 处理状态请求
*/
private async handleStatus(context: HttpRequestContext): Promise<void> {
const { response } = context;
this.sendJsonResponse(response, 200, {
success: true,
status: 'running',
connections: this.httpConnections.size,
uptime: process.uptime(),
serverTime: Date.now()
});
}
/**
* 读取请求体
*/
private readRequestBody(req: IncomingMessage): Promise<string> {
return new Promise((resolve, reject) => {
let body = '';
let totalSize = 0;
req.on('data', (chunk: Buffer) => {
totalSize += chunk.length;
if (totalSize > this.config.maxRequestSize!) {
reject(new Error('Request body too large'));
return;
}
body += chunk.toString();
});
req.on('end', () => {
resolve(body);
});
req.on('error', (error) => {
reject(error);
});
});
}
/**
* 发送长轮询响应
*/
private sendLongPollResponse(connection: HttpConnectionInfo, messages: TransportMessage[]): void {
if (!connection.longPollResponse || connection.longPollResponse.headersSent) {
return;
}
// 清理定时器
if (connection.longPollTimer) {
clearTimeout(connection.longPollTimer);
connection.longPollTimer = undefined;
}
this.sendJsonResponse(connection.longPollResponse, 200, {
success: true,
messages
});
connection.longPollResponse = undefined;
}
/**
* 设置 CORS 头
*/
private setCorsHeaders(res: ServerResponse): void {
const origins = this.config.corsOrigins!;
const origin = origins.includes('*') ? '*' : origins[0];
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Max-Age', '86400');
}
/**
* 发送 JSON 响应
*/
private sendJsonResponse(res: ServerResponse, statusCode: number, data: any): void {
if (res.headersSent) return;
res.setHeader('Content-Type', 'application/json');
res.writeHead(statusCode);
res.end(JSON.stringify(data));
}
/**
* 发送错误响应
*/
private sendErrorResponse(res: ServerResponse, statusCode: number, message: string): void {
if (res.headersSent) return;
this.sendJsonResponse(res, statusCode, {
success: false,
error: message,
code: statusCode
});
}
/**
* 清理连接资源
*/
private cleanupConnection(connectionId: string): void {
const connection = this.httpConnections.get(connectionId);
if (connection) {
if (connection.longPollTimer) {
clearTimeout(connection.longPollTimer);
}
if (connection.longPollResponse && !connection.longPollResponse.headersSent) {
this.sendJsonResponse(connection.longPollResponse, 200, {
success: true,
messages: [],
disconnected: true
});
}
this.httpConnections.delete(connectionId);
}
}
/**
* 清理所有资源
*/
private async cleanup(): Promise<void> {
// 清理所有连接
for (const connectionId of this.httpConnections.keys()) {
this.cleanupConnection(connectionId);
}
this.clearConnections();
// 关闭 HTTP 服务器
if (this.httpServer) {
await new Promise<void>((resolve) => {
this.httpServer!.close(() => resolve());
});
this.httpServer = null;
}
}
/**
* 获取 HTTP 连接统计信息
*/
getHttpStats(): {
totalConnections: number;
activeLongPolls: number;
queuedMessages: number;
} {
let activeLongPolls = 0;
let queuedMessages = 0;
for (const connection of this.httpConnections.values()) {
if (connection.longPollResponse && !connection.longPollResponse.headersSent) {
activeLongPolls++;
}
queuedMessages += connection.messageQueue.length;
}
return {
totalConnections: this.httpConnections.size,
activeLongPolls,
queuedMessages
};
}
}

View File

@@ -0,0 +1,452 @@
/**
* 网络服务器主类
*
* 整合 WebSocket 和 HTTP 传输,提供统一的网络服务接口
*/
import { EventEmitter } from 'events';
import { Transport, TransportConfig, TransportMessage } from './Transport';
import { WebSocketTransport, WebSocketTransportConfig } from './WebSocketTransport';
import { HttpTransport, HttpTransportConfig } from './HttpTransport';
import { ClientConnection, ClientConnectionState, ClientPermissions } from './ClientConnection';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
/**
* 网络服务器配置
*/
export interface NetworkServerConfig {
/** 服务器名称 */
name?: string;
/** WebSocket 配置 */
websocket?: WebSocketTransportConfig;
/** HTTP 配置 */
http?: HttpTransportConfig;
/** 默认客户端权限 */
defaultPermissions?: ClientPermissions;
/** 最大客户端连接数 */
maxConnections?: number;
/** 客户端认证超时(毫秒) */
authenticationTimeout?: number;
/** 是否启用统计 */
enableStats?: boolean;
}
/**
* 服务器统计信息
*/
export interface ServerStats {
/** 总连接数 */
totalConnections: number;
/** 当前活跃连接数 */
activeConnections: number;
/** 已认证连接数 */
authenticatedConnections: number;
/** 消息总数 */
totalMessages: number;
/** 错误总数 */
totalErrors: number;
/** 服务器启动时间 */
startTime: Date;
/** 服务器运行时间(毫秒) */
uptime: number;
}
/**
* 网络服务器事件
*/
export interface NetworkServerEvents {
/** 服务器启动 */
'server-started': () => void;
/** 服务器停止 */
'server-stopped': () => void;
/** 客户端连接 */
'client-connected': (client: ClientConnection) => void;
/** 客户端断开连接 */
'client-disconnected': (clientId: string, reason?: string) => void;
/** 客户端认证成功 */
'client-authenticated': (client: ClientConnection) => void;
/** 收到消息 */
'message': (client: ClientConnection, message: TransportMessage) => void;
/** 服务器错误 */
'error': (error: Error, clientId?: string) => void;
}
/**
* 网络服务器主类
*/
export class NetworkServer extends EventEmitter {
private config: NetworkServerConfig;
private wsTransport: WebSocketTransport | null = null;
private httpTransport: HttpTransport | null = null;
private clients = new Map<string, ClientConnection>();
private isRunning = false;
private stats: ServerStats;
constructor(config: NetworkServerConfig) {
super();
this.config = {
name: 'NetworkServer',
maxConnections: 1000,
authenticationTimeout: 30000, // 30秒
enableStats: true,
defaultPermissions: {
canJoinRooms: true,
canCreateRooms: false,
canSendRpc: true,
canSyncVars: true
},
...config
};
this.stats = {
totalConnections: 0,
activeConnections: 0,
authenticatedConnections: 0,
totalMessages: 0,
totalErrors: 0,
startTime: new Date(),
uptime: 0
};
this.initialize();
}
/**
* 启动服务器
*/
async start(): Promise<void> {
if (this.isRunning) {
throw new Error('Server is already running');
}
try {
const promises: Promise<void>[] = [];
// 启动 WebSocket 传输
if (this.config.websocket && this.wsTransport) {
promises.push(this.wsTransport.start());
}
// 启动 HTTP 传输
if (this.config.http && this.httpTransport) {
promises.push(this.httpTransport.start());
}
if (promises.length === 0) {
throw new Error('No transport configured. Please configure at least one transport (WebSocket or HTTP)');
}
await Promise.all(promises);
this.isRunning = true;
this.stats.startTime = new Date();
console.log(`Network Server "${this.config.name}" started successfully`);
if (this.config.websocket) {
console.log(`- WebSocket: ws://${this.config.websocket.host || 'localhost'}:${this.config.websocket.port}${this.config.websocket.path || '/ws'}`);
}
if (this.config.http) {
console.log(`- HTTP: http://${this.config.http.host || 'localhost'}:${this.config.http.port}${this.config.http.apiPrefix || '/api'}`);
}
this.emit('server-started');
} catch (error) {
await this.stop();
throw error;
}
}
/**
* 停止服务器
*/
async stop(): Promise<void> {
if (!this.isRunning) {
return;
}
this.isRunning = false;
// 断开所有客户端
const clients = Array.from(this.clients.values());
for (const client of clients) {
client.disconnect('server-shutdown');
}
// 停止传输层
const promises: Promise<void>[] = [];
if (this.wsTransport) {
promises.push(this.wsTransport.stop());
}
if (this.httpTransport) {
promises.push(this.httpTransport.stop());
}
await Promise.all(promises);
console.log(`Network Server "${this.config.name}" stopped`);
this.emit('server-stopped');
}
/**
* 获取服务器配置
*/
getConfig(): Readonly<NetworkServerConfig> {
return this.config;
}
/**
* 获取服务器统计信息
*/
getStats(): ServerStats {
this.stats.uptime = Date.now() - this.stats.startTime.getTime();
this.stats.activeConnections = this.clients.size;
this.stats.authenticatedConnections = Array.from(this.clients.values())
.filter(client => client.isAuthenticated).length;
return { ...this.stats };
}
/**
* 获取所有客户端连接
*/
getClients(): ClientConnection[] {
return Array.from(this.clients.values());
}
/**
* 获取指定客户端连接
*/
getClient(clientId: string): ClientConnection | undefined {
return this.clients.get(clientId);
}
/**
* 检查客户端是否存在
*/
hasClient(clientId: string): boolean {
return this.clients.has(clientId);
}
/**
* 获取客户端数量
*/
getClientCount(): number {
return this.clients.size;
}
/**
* 发送消息给指定客户端
*/
async sendToClient(clientId: string, message: TransportMessage): Promise<boolean> {
const client = this.clients.get(clientId);
if (!client) {
return false;
}
return await client.sendMessage(message);
}
/**
* 广播消息给所有客户端
*/
async broadcast(message: TransportMessage, excludeId?: string): Promise<number> {
const promises = Array.from(this.clients.entries())
.filter(([clientId]) => clientId !== excludeId)
.map(([, client]) => client.sendMessage(message));
const results = await Promise.allSettled(promises);
return results.filter(result => result.status === 'fulfilled' && result.value).length;
}
/**
* 发送消息给指定房间的所有客户端
*/
async broadcastToRoom(roomId: string, message: TransportMessage, excludeId?: string): Promise<number> {
const roomClients = Array.from(this.clients.values())
.filter(client => client.currentRoomId === roomId && client.id !== excludeId);
const promises = roomClients.map(client => client.sendMessage(message));
const results = await Promise.allSettled(promises);
return results.filter(result => result.status === 'fulfilled' && result.value).length;
}
/**
* 断开指定客户端连接
*/
async disconnectClient(clientId: string, reason?: string): Promise<void> {
const client = this.clients.get(clientId);
if (client) {
client.disconnect(reason);
}
}
/**
* 获取在指定房间的客户端列表
*/
getClientsInRoom(roomId: string): ClientConnection[] {
return Array.from(this.clients.values())
.filter(client => client.currentRoomId === roomId);
}
/**
* 检查服务器是否正在运行
*/
isServerRunning(): boolean {
return this.isRunning;
}
/**
* 初始化服务器
*/
private initialize(): void {
// 初始化 WebSocket 传输
if (this.config.websocket) {
this.wsTransport = new WebSocketTransport(this.config.websocket);
this.setupTransportEvents(this.wsTransport);
}
// 初始化 HTTP 传输
if (this.config.http) {
this.httpTransport = new HttpTransport(this.config.http);
this.setupTransportEvents(this.httpTransport);
}
}
/**
* 设置传输层事件监听
*/
private setupTransportEvents(transport: Transport): void {
transport.on('client-connected', (connectionInfo) => {
this.handleClientConnected(connectionInfo.id, connectionInfo.remoteAddress || 'unknown', transport);
});
transport.on('client-disconnected', (connectionId, reason) => {
this.handleClientDisconnected(connectionId, reason);
});
transport.on('message', (connectionId, message) => {
this.handleTransportMessage(connectionId, message);
});
transport.on('error', (error, connectionId) => {
this.handleTransportError(error, connectionId);
});
}
/**
* 处理客户端连接
*/
private handleClientConnected(connectionId: string, remoteAddress: string, transport: Transport): void {
// 检查连接数限制
if (this.config.maxConnections && this.clients.size >= this.config.maxConnections) {
transport.disconnectClient(connectionId, 'Max connections reached');
return;
}
const client = new ClientConnection(
connectionId,
remoteAddress,
(message) => transport.sendToClient(connectionId, message),
{
connectionTimeout: this.config.authenticationTimeout,
permissions: this.config.defaultPermissions
}
);
// 设置客户端事件监听
this.setupClientEvents(client);
this.clients.set(connectionId, client);
this.stats.totalConnections++;
console.log(`Client connected: ${connectionId} from ${remoteAddress}`);
this.emit('client-connected', client);
}
/**
* 处理客户端断开连接
*/
private handleClientDisconnected(connectionId: string, reason?: string): void {
const client = this.clients.get(connectionId);
if (client) {
client.destroy();
this.clients.delete(connectionId);
console.log(`Client disconnected: ${connectionId}, reason: ${reason || 'unknown'}`);
this.emit('client-disconnected', connectionId, reason);
}
}
/**
* 处理传输层消息
*/
private handleTransportMessage(connectionId: string, message: TransportMessage): void {
const client = this.clients.get(connectionId);
if (!client) {
return;
}
client.handleMessage(message);
this.stats.totalMessages++;
this.emit('message', client, message);
}
/**
* 处理传输层错误
*/
private handleTransportError(error: Error, connectionId?: string): void {
this.stats.totalErrors++;
console.error(`Transport error${connectionId ? ` (client: ${connectionId})` : ''}:`, error.message);
this.emit('error', error, connectionId);
// 如果是特定客户端的错误,断开该客户端
if (connectionId) {
this.disconnectClient(connectionId, 'transport-error');
}
}
/**
* 设置客户端事件监听
*/
private setupClientEvents(client: ClientConnection): void {
client.on('authenticated', (userData) => {
console.log(`Client authenticated: ${client.id}`, userData);
this.emit('client-authenticated', client);
});
client.on('error', (error) => {
console.error(`Client error (${client.id}):`, error.message);
this.emit('error', error, client.id);
});
client.on('timeout', () => {
console.log(`Client timeout: ${client.id}`);
this.disconnectClient(client.id, 'timeout');
});
client.on('state-changed', (oldState, newState) => {
console.log(`Client ${client.id} state changed: ${oldState} -> ${newState}`);
});
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof NetworkServerEvents>(event: K, listener: NetworkServerEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof NetworkServerEvents>(event: K, ...args: Parameters<NetworkServerEvents[K]>): boolean {
return super.emit(event, ...args);
}
}

View File

@@ -0,0 +1,224 @@
/**
* 网络传输层抽象接口
*/
import { EventEmitter } from 'events';
import { NetworkMessage, NetworkValue } from '@esengine/ecs-framework-network-shared';
/**
* 传输层配置
*/
export interface TransportConfig {
/** 服务器端口 */
port: number;
/** 主机地址 */
host?: string;
/** 最大连接数 */
maxConnections?: number;
/** 心跳间隔(毫秒) */
heartbeatInterval?: number;
/** 连接超时(毫秒) */
connectionTimeout?: number;
}
/**
* 客户端连接信息
*/
export interface ClientConnectionInfo {
/** 连接ID */
id: string;
/** 客户端IP */
remoteAddress?: string;
/** 连接时间 */
connectedAt: Date;
/** 最后活跃时间 */
lastActivity: Date;
/** 用户数据 */
userData?: Record<string, NetworkValue>;
}
/**
* 网络消息包装
*/
export interface TransportMessage {
/** 消息类型 */
type: 'rpc' | 'syncvar' | 'system' | 'custom';
/** 消息数据 */
data: NetworkValue;
/** 发送者ID */
senderId?: string;
/** 目标客户端ID(可选,用于单播) */
targetId?: string;
/** 是否可靠传输 */
reliable?: boolean;
}
/**
* 网络传输层事件
*/
export interface TransportEvents {
/** 客户端连接 */
'client-connected': (connectionInfo: ClientConnectionInfo) => void;
/** 客户端断开连接 */
'client-disconnected': (connectionId: string, reason?: string) => void;
/** 收到消息 */
'message': (connectionId: string, message: TransportMessage) => void;
/** 传输错误 */
'error': (error: Error, connectionId?: string) => void;
/** 服务器启动 */
'server-started': (config: TransportConfig) => void;
/** 服务器关闭 */
'server-stopped': () => void;
}
/**
* 网络传输层抽象类
*/
export abstract class Transport extends EventEmitter {
protected config: TransportConfig;
protected isRunning = false;
protected connections = new Map<string, ClientConnectionInfo>();
constructor(config: TransportConfig) {
super();
this.config = config;
}
/**
* 启动传输层服务
*/
abstract start(): Promise<void>;
/**
* 停止传输层服务
*/
abstract stop(): Promise<void>;
/**
* 发送消息给指定客户端
*/
abstract sendToClient(connectionId: string, message: TransportMessage): Promise<boolean>;
/**
* 广播消息给所有客户端
*/
abstract broadcast(message: TransportMessage, excludeId?: string): Promise<number>;
/**
* 广播消息给指定客户端列表
*/
abstract sendToClients(connectionIds: string[], message: TransportMessage): Promise<number>;
/**
* 断开指定客户端连接
*/
abstract disconnectClient(connectionId: string, reason?: string): Promise<void>;
/**
* 获取在线客户端数量
*/
getConnectionCount(): number {
return this.connections.size;
}
/**
* 获取所有连接信息
*/
getConnections(): ClientConnectionInfo[] {
return Array.from(this.connections.values());
}
/**
* 获取指定连接信息
*/
getConnection(connectionId: string): ClientConnectionInfo | undefined {
return this.connections.get(connectionId);
}
/**
* 检查连接是否存在
*/
hasConnection(connectionId: string): boolean {
return this.connections.has(connectionId);
}
/**
* 服务器是否正在运行
*/
isServerRunning(): boolean {
return this.isRunning;
}
/**
* 获取传输层配置
*/
getConfig(): TransportConfig {
return { ...this.config };
}
/**
* 更新客户端最后活跃时间
*/
protected updateClientActivity(connectionId: string): void {
const connection = this.connections.get(connectionId);
if (connection) {
connection.lastActivity = new Date();
}
}
/**
* 添加客户端连接
*/
protected addConnection(connectionInfo: ClientConnectionInfo): void {
this.connections.set(connectionInfo.id, connectionInfo);
this.emit('client-connected', connectionInfo);
}
/**
* 移除客户端连接
*/
protected removeConnection(connectionId: string, reason?: string): void {
if (this.connections.delete(connectionId)) {
this.emit('client-disconnected', connectionId, reason);
}
}
/**
* 处理接收到的消息
*/
protected handleMessage(connectionId: string, message: TransportMessage): void {
this.updateClientActivity(connectionId);
this.emit('message', connectionId, message);
}
/**
* 处理传输错误
*/
protected handleError(error: Error, connectionId?: string): void {
this.emit('error', error, connectionId);
}
/**
* 清理所有连接
*/
protected clearConnections(): void {
const connectionIds = Array.from(this.connections.keys());
for (const id of connectionIds) {
this.removeConnection(id, 'server-shutdown');
}
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof TransportEvents>(event: K, listener: TransportEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof TransportEvents>(event: K, ...args: Parameters<TransportEvents[K]>): boolean {
return super.emit(event, ...args);
}
}

View File

@@ -0,0 +1,406 @@
/**
* WebSocket 传输层实现
*/
import { WebSocketServer, WebSocket } from 'ws';
import { createServer, Server as HttpServer } from 'http';
import { v4 as uuidv4 } from 'uuid';
import { Transport, TransportConfig, ClientConnectionInfo, TransportMessage } from './Transport';
/**
* WebSocket 传输配置
*/
export interface WebSocketTransportConfig extends TransportConfig {
/** WebSocket 路径 */
path?: string;
/** 是否启用压缩 */
compression?: boolean;
/** 最大消息大小(字节) */
maxMessageSize?: number;
/** ping 间隔(毫秒) */
pingInterval?: number;
/** pong 超时(毫秒) */
pongTimeout?: number;
}
/**
* WebSocket 客户端连接扩展信息
*/
interface WebSocketConnectionInfo extends ClientConnectionInfo {
/** WebSocket 实例 */
socket: WebSocket;
/** ping 定时器 */
pingTimer?: NodeJS.Timeout;
/** pong 超时定时器 */
pongTimer?: NodeJS.Timeout;
}
/**
* WebSocket 传输层实现
*/
export class WebSocketTransport extends Transport {
private httpServer: HttpServer | null = null;
private wsServer: WebSocketServer | null = null;
private wsConnections = new Map<string, WebSocketConnectionInfo>();
protected override config: WebSocketTransportConfig;
constructor(config: WebSocketTransportConfig) {
super(config);
this.config = {
path: '/ws',
compression: true,
maxMessageSize: 1024 * 1024, // 1MB
pingInterval: 30000, // 30秒
pongTimeout: 5000, // 5秒
heartbeatInterval: 30000,
connectionTimeout: 60000,
maxConnections: 1000,
...config
};
}
/**
* 启动 WebSocket 服务器
*/
async start(): Promise<void> {
if (this.isRunning) {
throw new Error('WebSocket transport is already running');
}
try {
// 创建 HTTP 服务器
this.httpServer = createServer();
// 创建 WebSocket 服务器
this.wsServer = new WebSocketServer({
server: this.httpServer,
path: this.config.path,
maxPayload: this.config.maxMessageSize,
perMessageDeflate: this.config.compression
});
// 设置事件监听
this.setupEventListeners();
// 启动服务器
await new Promise<void>((resolve, reject) => {
this.httpServer!.listen(this.config.port, this.config.host, (error?: Error) => {
if (error) {
reject(error);
} else {
this.isRunning = true;
resolve();
}
});
});
this.emit('server-started', this.config);
} catch (error) {
await this.cleanup();
throw error;
}
}
/**
* 停止 WebSocket 服务器
*/
async stop(): Promise<void> {
if (!this.isRunning) {
return;
}
this.isRunning = false;
// 断开所有客户端连接
for (const [connectionId, connection] of this.wsConnections) {
this.disconnectClient(connectionId, 'server-shutdown');
}
await this.cleanup();
this.emit('server-stopped');
}
/**
* 发送消息给指定客户端
*/
async sendToClient(connectionId: string, message: TransportMessage): Promise<boolean> {
const connection = this.wsConnections.get(connectionId);
if (!connection || connection.socket.readyState !== WebSocket.OPEN) {
return false;
}
try {
const data = JSON.stringify(message);
connection.socket.send(data);
this.updateClientActivity(connectionId);
return true;
} catch (error) {
this.handleError(error as Error, connectionId);
return false;
}
}
/**
* 广播消息给所有客户端
*/
async broadcast(message: TransportMessage, excludeId?: string): Promise<number> {
const data = JSON.stringify(message);
let sentCount = 0;
for (const [connectionId, connection] of this.wsConnections) {
if (excludeId && connectionId === excludeId) {
continue;
}
if (connection.socket.readyState === WebSocket.OPEN) {
try {
connection.socket.send(data);
sentCount++;
} catch (error) {
this.handleError(error as Error, connectionId);
}
}
}
return sentCount;
}
/**
* 发送消息给指定客户端列表
*/
async sendToClients(connectionIds: string[], message: TransportMessage): Promise<number> {
const data = JSON.stringify(message);
let sentCount = 0;
for (const connectionId of connectionIds) {
const connection = this.wsConnections.get(connectionId);
if (connection && connection.socket.readyState === WebSocket.OPEN) {
try {
connection.socket.send(data);
sentCount++;
} catch (error) {
this.handleError(error as Error, connectionId);
}
}
}
return sentCount;
}
/**
* 断开指定客户端连接
*/
async disconnectClient(connectionId: string, reason?: string): Promise<void> {
const connection = this.wsConnections.get(connectionId);
if (connection) {
this.cleanupConnection(connectionId);
connection.socket.close(1000, reason);
}
}
/**
* 设置事件监听器
*/
private setupEventListeners(): void {
if (!this.wsServer) return;
this.wsServer.on('connection', (socket: WebSocket, request) => {
this.handleNewConnection(socket, request);
});
this.wsServer.on('error', (error: Error) => {
this.handleError(error);
});
if (this.httpServer) {
this.httpServer.on('error', (error: Error) => {
this.handleError(error);
});
}
}
/**
* 处理新连接
*/
private handleNewConnection(socket: WebSocket, request: any): void {
// 检查连接数限制
if (this.config.maxConnections && this.wsConnections.size >= this.config.maxConnections) {
socket.close(1013, 'Too many connections');
return;
}
const connectionId = uuidv4();
const remoteAddress = request.socket.remoteAddress || request.headers['x-forwarded-for'] || 'unknown';
const connectionInfo: WebSocketConnectionInfo = {
id: connectionId,
socket,
remoteAddress: Array.isArray(remoteAddress) ? remoteAddress[0] : remoteAddress,
connectedAt: new Date(),
lastActivity: new Date(),
userData: {}
};
this.wsConnections.set(connectionId, connectionInfo);
this.addConnection(connectionInfo);
// 设置 socket 事件监听
socket.on('message', (data: Buffer) => {
this.handleClientMessage(connectionId, data);
});
socket.on('close', (code: number, reason: Buffer) => {
this.handleClientDisconnect(connectionId, code, reason.toString());
});
socket.on('error', (error: Error) => {
this.handleError(error, connectionId);
this.handleClientDisconnect(connectionId, 1006, 'Socket error');
});
socket.on('pong', () => {
this.handlePong(connectionId);
});
// 启动心跳检测
this.startHeartbeat(connectionId);
}
/**
* 处理客户端消息
*/
private handleClientMessage(connectionId: string, data: Buffer): void {
try {
const message = JSON.parse(data.toString()) as TransportMessage;
message.senderId = connectionId;
this.handleMessage(connectionId, message);
} catch (error) {
this.handleError(new Error(`Invalid message format from client ${connectionId}`), connectionId);
}
}
/**
* 处理客户端断开连接
*/
private handleClientDisconnect(connectionId: string, code: number, reason: string): void {
this.cleanupConnection(connectionId);
this.removeConnection(connectionId, `${code}: ${reason}`);
}
/**
* 启动心跳检测
*/
private startHeartbeat(connectionId: string): void {
const connection = this.wsConnections.get(connectionId);
if (!connection) return;
if (this.config.pingInterval && this.config.pingInterval > 0) {
connection.pingTimer = setInterval(() => {
this.sendPing(connectionId);
}, this.config.pingInterval);
}
}
/**
* 发送 ping
*/
private sendPing(connectionId: string): void {
const connection = this.wsConnections.get(connectionId);
if (!connection || connection.socket.readyState !== WebSocket.OPEN) {
return;
}
connection.socket.ping();
// 设置 pong 超时
if (this.config.pongTimeout && this.config.pongTimeout > 0) {
if (connection.pongTimer) {
clearTimeout(connection.pongTimer);
}
connection.pongTimer = setTimeout(() => {
this.disconnectClient(connectionId, 'Pong timeout');
}, this.config.pongTimeout);
}
}
/**
* 处理 pong 响应
*/
private handlePong(connectionId: string): void {
const connection = this.wsConnections.get(connectionId);
if (connection && connection.pongTimer) {
clearTimeout(connection.pongTimer);
connection.pongTimer = undefined;
}
this.updateClientActivity(connectionId);
}
/**
* 清理连接资源
*/
private cleanupConnection(connectionId: string): void {
const connection = this.wsConnections.get(connectionId);
if (connection) {
if (connection.pingTimer) {
clearInterval(connection.pingTimer);
}
if (connection.pongTimer) {
clearTimeout(connection.pongTimer);
}
this.wsConnections.delete(connectionId);
}
}
/**
* 清理所有资源
*/
private async cleanup(): Promise<void> {
// 清理所有连接
for (const connectionId of this.wsConnections.keys()) {
this.cleanupConnection(connectionId);
}
this.clearConnections();
// 关闭 WebSocket 服务器
if (this.wsServer) {
this.wsServer.close();
this.wsServer = null;
}
// 关闭 HTTP 服务器
if (this.httpServer) {
await new Promise<void>((resolve) => {
this.httpServer!.close(() => resolve());
});
this.httpServer = null;
}
}
/**
* 获取 WebSocket 连接统计信息
*/
getWebSocketStats(): {
totalConnections: number;
activeConnections: number;
inactiveConnections: number;
} {
let activeConnections = 0;
let inactiveConnections = 0;
for (const connection of this.wsConnections.values()) {
if (connection.socket.readyState === WebSocket.OPEN) {
activeConnections++;
} else {
inactiveConnections++;
}
}
return {
totalConnections: this.wsConnections.size,
activeConnections,
inactiveConnections
};
}
}

View File

@@ -0,0 +1,9 @@
/**
* 核心模块导出
*/
export * from './Transport';
export * from './WebSocketTransport';
export * from './HttpTransport';
export * from './ClientConnection';
export * from './NetworkServer';