server framework

This commit is contained in:
YJH143
2022-08-29 00:23:08 +08:00
parent 5814812c9f
commit 0e3f95a0da
7 changed files with 262 additions and 3 deletions

View File

@@ -1,5 +1,62 @@
import {WebSocket} from "ws"
import * as http from "http"
import { HttpUtil } from "./models/HttpUtil"
import { buffer } from "stream/consumers"
export interface WsConnectionOptions {
connId: number
ws: WebSocket
httpReq: http.IncomingMessage
onClose: (conn: WsConnection, code: number, reason: string) => void
onRecvData: (data: Buffer) => void;
}
export class WsConnection {
options: WsConnectionOptions
ip!: string
constructor (options: WsConnectionOptions) {
this.options = options;
}
getConnId() : number {
return this.options.connId;
}
getIp() : string {
return this.ip;
}
getWs() : WebSocket {
return this.options.ws;
}
init(options: WsConnectionOptions) {
this.options = options;
this.ip = HttpUtil.getClientIp(options.httpReq);
// todo: log
this.options.ws.onclose = e => { this.options.onClose(this, e.code, e.reason);}
this.options.ws.onerror = e => { console.log("[client_err] ", e.error)}
this.options.ws.onmessage = e => {
if (Buffer.isBuffer(e.data)) {
this.options.onRecvData(e.data);
} else {
console.log("[dataType_err]", e.data);
}
}
}
close(reason?: string) {
if (this.options.ws && this.options.ws.readyState == WebSocket.OPEN) {
this.options.ws.close(1000, reason || 'Server Closed');
}
this.options.ws.onopen = this.options.ws.onclose = this.options.ws.onmessage = this.options.ws.onerror = undefined as any;
this.ip = undefined;
}
getIsClosed(): boolean {
return this.options.ws.readyState !== WebSocket.OPEN;
}
}

View File

@@ -0,0 +1,25 @@
export class Counter {
private _min: number;
private _max: number;
private _last: number;
constructor(min: number = 1, max: number = Number.MAX_SAFE_INTEGER) {
this._min = min;
this._max = max;
this._last = max;
}
/** 复位从新从0开始计数 */
reset() {
this._last = this._max;
}
getNext() {
return this._last >= this._max ? (this._last = this._min) : ++this._last;
}
get last() {
return this._last;
}
}

View File

@@ -0,0 +1,16 @@
import * as http from "http";
export class HttpUtil {
static getClientIp(req: http.IncomingMessage) {
var ipAddress;
var forwardedIpsStr = req.headers['x-forwarded-for'] as string | undefined;
if (forwardedIpsStr) {
var forwardedIps = forwardedIpsStr.split(',');
ipAddress = forwardedIps[0];
}
if (!ipAddress) {
ipAddress = req.socket.remoteAddress;
}
return ipAddress ? ipAddress.replace(/^::ffff:/, '') : '';
};
}

View File

@@ -0,0 +1,32 @@
import { WsConnection } from "../connection";
export class ConnManager {
private static _idConnMap : {[connId: number]: WsConnection | undefined};
private static activeConnNum : number = 0;
public static getActiveConnNum() {
return this.activeConnNum;
}
public static getConn(connId : number) {
return this._idConnMap[connId];
}
public static addConn(conn : WsConnection) {
this._idConnMap[conn.options.connId] = conn
this.activeConnNum++;
}
public static remConn(conn : WsConnection) {
this._idConnMap[conn.options.connId] = undefined;
this.activeConnNum--;
}
public static sendMsg(connId: number, msg : Buffer) {
let conn = this._idConnMap[connId];
if (conn === undefined) {
console.log("conn is not exists");
}
conn.options.ws.send(msg)
}
}

View File

@@ -1,6 +1,9 @@
import * as WebSocket from 'ws';
import { Server as WebSocketServer } from 'ws';
import {WsConnection} from "./connection"
import * as http from "http"
import { Counter } from './models/Counter';
import { ConnManager } from './models/connMgr';
enum WsServerStatus {
Initializing,
@@ -9,7 +12,91 @@ enum WsServerStatus {
Closed,
}
export class WsServer {
private _id2conn : {[connId : string] : WsConnection | undefined}
export interface WsServerOptions {
host: string
port: number
timeout: number
onClientConnect: (ws : WebSocket, httpReq: http.IncomingMessage) => void
}
const defaultWsServerOptions: WsServerOptions = {
host: "0.0.0.0",
port: 8080,
timeout: 3000,
onClientConnect: undefined,
}
export class WsServer {
private status: WsServerStatus = WsServerStatus.Closed;
private options: WsServerOptions;
private _wsServer: WebSocketServer;
private _connIdCounter = new Counter(1);
constructor (options?: WsServerOptions) {
this.options = Object.assign({}, defaultWsServerOptions, options);
if (this.options.onClientConnect === undefined) {
this.options.onClientConnect = this._onClientConnect;
}
}
start() {
if (this._wsServer) {
throw new Error('Server already started');
}
this.status = WsServerStatus.Initializing
this._wsServer = new WebSocketServer({port: this.options.port, host: this.options.host}, () => {
console.log(`server started, listening on ${this.options.host}:${this.options.port}...`)
this.status = WsServerStatus.Inited
})
this._wsServer.on("connection", this.options.onClientConnect)
this._wsServer.on("error", e => {
console.log("[server_error]", e)
})
}
private _onClientConnect = (ws: WebSocket, httpReq: http.IncomingMessage) => {
// 服务不可用 不接受新的连接
if (this.status !== WsServerStatus.Inited) {
ws.close();
return;
}
let connId = this.getNextConnId()
if (isNaN(connId)) {
ws.close(-1, "无法建立更多的连接");
return;
}
let conn = new WsConnection({
connId: connId,
ws: ws,
httpReq: httpReq,
onClose: this._onClientClose,
onRecvData: v => { this.onData(conn, v) }
});
ConnManager.addConn(conn);
console.log('[Connected]', `ActiveConn=${ConnManager.getActiveConnNum()}`)
};
getNextConnId(): number {
for (let i = 0; i < 1000; ++i) {
let connId = this._connIdCounter.getNext();
if (!ConnManager.getConn(connId)) {
return connId;
}
}
return NaN;
}
private _onClientClose = (conn: WsConnection, code: number, reason: string) => {
ConnManager.remConn(conn)
console.log('[Disconnected]', `Code=${code} ${reason ? `Reason=${reason} ` : ''} ActiveConn=${ConnManager.getActiveConnNum()}`)
}
private onData(conn : WsConnection, data: Buffer) {
console.log("conn on data", data)
}
}

39
package-lock.json generated
View File

@@ -8,9 +8,30 @@
"name": "cocos_creator_framework",
"version": "3.5.1",
"dependencies": {
"@types/node": "^18.7.13",
"@types/ws": "^8.5.3",
"http": "^0.0.1-security",
"ws": "^8.8.1"
}
},
"node_modules/@types/node": {
"version": "18.7.13",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.13.tgz",
"integrity": "sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw=="
},
"node_modules/@types/ws": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
"integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/http": {
"version": "0.0.1-security",
"resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz",
"integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g=="
},
"node_modules/ws": {
"version": "8.8.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz",
@@ -33,6 +54,24 @@
}
},
"dependencies": {
"@types/node": {
"version": "18.7.13",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.13.tgz",
"integrity": "sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw=="
},
"@types/ws": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
"integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==",
"requires": {
"@types/node": "*"
}
},
"http": {
"version": "0.0.1-security",
"resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz",
"integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g=="
},
"ws": {
"version": "8.8.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz",

View File

@@ -7,6 +7,9 @@
"version": "3.5.1"
},
"dependencies": {
"@types/node": "^18.7.13",
"@types/ws": "^8.5.3",
"http": "^0.0.1-security",
"ws": "^8.8.1"
}
}