更新network库及core库优化
This commit is contained in:
478
packages/network-server/src/core/ClientConnection.ts
Normal file
478
packages/network-server/src/core/ClientConnection.ts
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
602
packages/network-server/src/core/HttpTransport.ts
Normal file
602
packages/network-server/src/core/HttpTransport.ts
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
452
packages/network-server/src/core/NetworkServer.ts
Normal file
452
packages/network-server/src/core/NetworkServer.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
224
packages/network-server/src/core/Transport.ts
Normal file
224
packages/network-server/src/core/Transport.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
406
packages/network-server/src/core/WebSocketTransport.ts
Normal file
406
packages/network-server/src/core/WebSocketTransport.ts
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
9
packages/network-server/src/core/index.ts
Normal file
9
packages/network-server/src/core/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 核心模块导出
|
||||
*/
|
||||
|
||||
export * from './Transport';
|
||||
export * from './WebSocketTransport';
|
||||
export * from './HttpTransport';
|
||||
export * from './ClientConnection';
|
||||
export * from './NetworkServer';
|
||||
Reference in New Issue
Block a user