UNPKG

imsdk-server-core

Version:

轻量级Web服务器框架、WebSocket服务器框架。采用Typescript编写,简单易用。

713 lines (712 loc) 29.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WssServer = void 0; /** * 对ws封装的类 * ws相关信息:https://github.com/websockets/ws */ const uuid_1 = require("uuid"); const ws_1 = __importDefault(require("ws")); const https_1 = __importDefault(require("https")); const http_1 = __importDefault(require("http")); const WssSession_1 = require("./WssSession"); const AllExport_1 = require("../client/AllExport"); class WssServer { /** * @param context 上下文包装类实例 * @param category 日志分类 * @param config 配置信息 * @param wsscfg 库ws配置信息,参考依赖库 https://github.com/websockets/ws * * 本类将过滤掉wsscfg.host参数和wsscfg.port参数,请通过context来传入 */ constructor(context, category, config = {}, wsscfg = {}) { this._context = context; this._config = { pwd: null, secret: null, binary: false, cycle: 60 * 1000, timeout: 60 * 1000 * 3, reqIdCache: 32 }; Object.assign(this._config, config); //拷贝配置信息 //绑定log4js实例 this._logger = context.getLogger(category); //处理wsscfg if (wsscfg.host) this._logger.warn('ingore wsscfg.host'); delete wsscfg.host; if (wsscfg.port) this._logger.warn('ingore wsscfg.port'); delete wsscfg.port; if (wsscfg.noServer) this._logger.warn('ingore wsscfg.noServer'); delete wsscfg.noServer; this._wsscfg = wsscfg.server ? {} : { server: context.ssls ? https_1.default.createServer(context.readSSLKerCert()) : http_1.default.createServer() }; Object.assign(this._wsscfg, wsscfg); //拷贝ws配置信息 //绑定app和server this._wssapp = new ws_1.default.Server(this._wsscfg); //创建ws应用实例 this._server = this._wsscfg.server; //绑定HTTP/S服务器实例 //其它属性 this._routerMap = {}; this._remoteMap = {}; this._socketMap = {}; this._sessionMap = {}; this._channelMap = {}; this._clusterMap = {}; this._totalSocket = 0; this._totalSession = 0; this._cycleTicker = null; //定时器 this._serverCyclerListener = null; this._sessionCloseListener = null; } /** * 初始化集群 */ initClusters() { const heartick = Math.floor(this._config.cycle / 1000); for (let i = 0; i < this._context.links.length; i++) { const appName = this._context.links[i]; const address = this._context.nodes[appName]; const cluster = []; for (let k = 0; k < address.length; k++) { const url = (address[k].ssls ? 'wss://' : 'ws://') + (address[k].inip || address[k].host) + ':' + address[k].port; cluster.push({ grp: appName, url: url, rmc: new AllExport_1.WssBridge(url, this._config.pwd, this._config.binary, 8000, heartick, 2), }); } if (cluster.length > 0) { this._clusterMap[appName] = cluster; } } } /** * 设置周期监听器 * @param serverCyclerListener * @param sessionCloseListener */ setListeners(serverCyclerListener, sessionCloseListener) { this._serverCyclerListener = serverCyclerListener; this._sessionCloseListener = sessionCloseListener; } /** * 设置路由监听器 * @param route * @param listener */ setRouter(route, listener) { this._routerMap[route] = listener; } /** * 设置远程监听器 * @param route * @param listener */ setRemote(route, listener) { this._remoteMap[route] = listener; } /** * 绑定uid到session * @param session * @param uid * @param closeold */ bindUid(session, uid, closeold = false) { //旧session处理 const sessionold = this._sessionMap[uid.toString()]; if (sessionold) { this.unbindUid(sessionold); //解绑uid对应的旧session(此步骤务必在close之前执行,否则close事件中,会将uid对应的新session移除掉) if (closeold) sessionold.close(RouteCode.CODE_NEWBIND.code, RouteCode.CODE_NEWBIND.data); //关闭旧的session } //新session处理 this.unbindUid(session); //新session解绑旧的uid session.bindUid(uid); //新session绑定新的的uid this._sessionMap[uid.toString()] = session; //新session绑定到_sessionMap this._logger.debug('bindUid:', session.ip, session.id, session.uid); } ; /** * 解绑session的uid * @param session */ unbindUid(session) { if (!session.isBinded()) return; this._logger.debug('unbindUid:', session.ip, session.id, session.uid); delete this._sessionMap[session.uid.toString()]; //从_sessionMap中移除 session.unbindUid(); } /** * 根据uid从本节点获取session * @param uid */ getSession(uid) { return this._sessionMap[uid.toString()]; } /** * 加入本节点的某个消息推送组 * @param session * @param gid */ joinChannel(session, gid) { const channel = this._channelMap[gid.toString()] || { count: 0, sessions: {} }; if (!channel.sessions[session.id]) { channel.sessions[session.id] = session; channel.count++; session.joinChannel(gid); } this._channelMap[gid.toString()] = channel; this._logger.debug('joinChannel:', session.ip, session.id, session.uid, gid); } /** * 退出本节点的某个消息推送组 * @param session * @param gid */ quitChannel(session, gid) { const channel = this._channelMap[gid.toString()]; if (!channel) return; if (channel.sessions[session.id]) { delete channel.sessions[session.id]; channel.count--; session.quitChannel(gid); } if (channel.count <= 0) delete this._channelMap[gid.toString()]; this._logger.debug('quitChannel:', session.ip, session.id, session.uid, gid); } /** * 删除本节点的某个消息推送组 * @param gid */ deleteChannel(gid) { const channel = this._channelMap[gid.toString()]; if (!channel) return; for (let id in channel.sessions) { const session = channel.sessions[id]; session.quitChannel(gid); } delete this._channelMap[gid.toString()]; this._logger.debug('deleteChannel:', gid); } /** * 响应本节点的某个session的请求 * @param session * @param reqPack * @param message */ response(session, reqPack, message) { const pack = new AllExport_1.WssBridgePackData(RouteCode.ROUTE_RESPONSE, reqPack.reqId, message); const data = AllExport_1.WssBridgePackData.serialize(pack, this._config.pwd, this._config.binary); session.send(data, this._getSendOptions()); this._logger.debug('response:', session.ip, session.id, session.uid, pack); } /** * 推送消息到本节点的某个session * @param uid * @param route * @param message */ pushSession(uid, route, message) { const session = this._sessionMap[uid.toString()]; if (!session) return; const pack = new AllExport_1.WssBridgePackData(route, undefined, message); const data = AllExport_1.WssBridgePackData.serialize(pack, this._config.pwd, this._config.binary); session.send(data, this._getSendOptions()); this._logger.debug('pushSession:', session.ip, session.id, session.uid, pack); } /** * 推送消息到本节点的某批session * @param uids * @param route * @param message */ pushSessionBatch(uids, route, message) { const pack = new AllExport_1.WssBridgePackData(route, undefined, message); const data = AllExport_1.WssBridgePackData.serialize(pack, this._config.pwd, this._config.binary); for (let i = 0; i < uids.length; i++) { const session = this._sessionMap[uids[i].toString()]; if (session) { session.send(data, this._getSendOptions()); } } this._logger.debug('pushSessionBatch:', uids, pack); } /** * 推送消息到本节点的某个消息推送组 * @param gid * @param route * @param message */ pushChannel(gid, route, message) { const channel = this._channelMap[gid.toString()]; if (!channel) return; const pack = new AllExport_1.WssBridgePackData(route, undefined, message); const data = AllExport_1.WssBridgePackData.serialize(pack, this._config.pwd, this._config.binary); for (let id in channel.sessions) { const session = channel.sessions[id]; session.send(data, this._getSendOptions()); } this._logger.debug('pushChannel:', gid, pack); } /** * 推送消息到本节点的某个消息推送组,每个成员的数据都进过差异处理 * @param gid * @param route * @param message * @param customCallback 在这个函数中对每个成员的数据进行差异处理 */ pushChannelCustom(gid, route, message, customCallback) { const channel = this._channelMap[gid.toString()]; if (!channel) return; for (let id in channel.sessions) { const session = channel.sessions[id]; const pack = new AllExport_1.WssBridgePackData(route, undefined, customCallback(session.uid, message)); const data = AllExport_1.WssBridgePackData.serialize(pack, this._config.pwd, this._config.binary); session.send(data, this._getSendOptions()); this._logger.debug('pushChannelCustom:', session.ip, session.id, session.uid, gid, pack); } } /** * 推送消息到本节点的已经绑定过uid的全部session * @param route * @param message */ broadcast(route, message) { const pack = new AllExport_1.WssBridgePackData(route, undefined, message); const data = AllExport_1.WssBridgePackData.serialize(pack, this._config.pwd, this._config.binary); for (let uid in this._sessionMap) { const session = this._sessionMap[uid]; session.send(data, this._getSendOptions()); } this._logger.debug('broadcast:', pack); } /** * 推送消息到某个节点的某个session,建议通过dispatchCallback来优化推送性能 * @param appName 节点分组名 * @param uid * @param route * @param message * @param dispatchCallback 分配节点,如果未指定该函数,则从该节点分组的全部节点中搜索对应uid的session */ pushClusterSession(appName, uid, route, message, dispatchCallback) { const cluster = this._clusterMap[appName]; const innerData = this._generateInnerData(uid, route, message); if (dispatchCallback) { const handle = cluster[dispatchCallback(cluster, uid, innerData)]; handle.rmc.request(RouteCode.ROUTE_INNERP2P, innerData); this._logger.debug('pushClusterSession:', appName, handle.url, innerData); } else { for (let i = 0; i < cluster.length; i++) { const handle = cluster[i]; handle.rmc.request(RouteCode.ROUTE_INNERP2P, innerData); this._logger.debug('pushClusterSession:', appName, handle.url, innerData); } } } /** * 推送消息到某个节点的某个消息推送组,建议通过dispatchCallback来优化推送性能 * @param appName 节点分组名 * @param gid * @param route * @param message * @param dispatchCallback 分配节点,如果未指定该函数,则从该节点分组的全部节点中搜索对应gid的channel */ pushClusterChannel(appName, gid, route, message, dispatchCallback) { const cluster = this._clusterMap[appName]; const innerData = this._generateInnerData(gid, route, message); if (dispatchCallback) { const handle = cluster[dispatchCallback(cluster, gid, innerData)]; handle.rmc.request(RouteCode.ROUTE_INNERGRP, innerData); this._logger.debug('pushClusterChannel:', appName, handle.url, innerData); } else { for (let i = 0; i < cluster.length; i++) { const handle = cluster[i]; handle.rmc.request(RouteCode.ROUTE_INNERGRP, innerData); this._logger.debug('pushClusterChannel:', appName, handle.url, innerData); } } } /** * 推送消息到某个节点的已经绑定过uid的全部session * @param appName 节点分组名 * @param route * @param message * @param dispatchCallback 分配节点,如果未指定该函数,将推送到该节点分组的全部节点 */ clusterBroadcast(appName, route, message, dispatchCallback) { const cluster = this._clusterMap[appName]; const innerData = this._generateInnerData(null, route, message); if (dispatchCallback) { const handle = cluster[dispatchCallback(cluster, null, innerData)]; handle.rmc.request(RouteCode.ROUTE_INNERALL, innerData); this._logger.debug('clusterBroadcast:', appName, handle.url, innerData); } else { for (let i = 0; i < cluster.length; i++) { const handle = cluster[i]; handle.rmc.request(RouteCode.ROUTE_INNERALL, innerData); this._logger.debug('clusterBroadcast:', appName, handle.url, innerData); } } } /** * 节点间远程路由异步调用 * @param appName 节点分组名 * @param route * @param message * @param dispatchCallback 分配节点,如果未指定该函数,则从该节点分组的全部节点中随机选择一个节点 */ callRemote(appName, route, message, dispatchCallback) { const cluster = this._clusterMap[appName]; const innerData = this._generateInnerData(null, route, message); const index = dispatchCallback ? dispatchCallback(cluster, null, innerData) : Math.min(Math.floor(Math.random() * cluster.length), cluster.length - 1); const handle = cluster[index]; this._logger.debug('callRemote:', appName, handle.url, innerData); handle.rmc.request(RouteCode.ROUTE_INNERRMC, innerData); } /** * 节点间远程路由异步调用,并返回结果 * @param appName 节点分组名 * @param route * @param message * @param dispatchCallback 分配节点,如果未指定该函数,则从该节点分组的全部节点中随机选择一个节点 */ callRemoteForResult(appName, route, message, dispatchCallback) { const cluster = this._clusterMap[appName]; const msgdata = this._generateInnerData(null, route, message); const index = dispatchCallback ? dispatchCallback(cluster, null, msgdata) : Math.min(Math.floor(Math.random() * cluster.length), cluster.length - 1); const handle = cluster[index]; this._logger.debug('callRemoteForResult:', appName, handle.url, msgdata); return new Promise((resolve) => { handle.rmc.request(RouteCode.ROUTE_INNERRMC, msgdata, (resp, params) => { resolve(resp); }, (resp, params) => { resolve(resp); }, this); }); } /** * 开启服务器 * @param callback 服务器启动后的回调函数 */ start(callback) { //参数检测 if (this._config.cycle < 10000) throw Error('cycle >= 10,000ms'); if (this._config.timeout < 30000) throw Error('timeout >= 30,000ms'); if (this._config.cycle * 3 > this._config.timeout) throw Error('timeout >= cycle * 3'); //注册监听 this._wssapp.on('connection', (socket, request) => { this._onWebSocketConnection(socket, request); }); //开启心跳循环 this._cycleTicker = setInterval(() => { try { this._onServerLifeCycle(); } catch (e) { this._logger.error('Unhandled life cycle exception:', e); } }, this._config.cycle); //连接关联的集群节点 for (let appName in this._clusterMap) { const cluster = this._clusterMap[appName]; for (let i = 0; i < cluster.length; i++) { this._connectForCluster(cluster[i]); } } //启动服务器 this._server.listen(this._context.port, () => { this._logger.info('ssls', this._context.ssls, this._context.host, this._context.port, 'is listening...'); if (callback) callback(); }); } /** * 关闭服务器 * @param callback */ close(callback) { //销毁心跳循环 if (this._cycleTicker) { clearInterval(this._cycleTicker); this._cycleTicker = null; } //断开关联的集群节点 for (let appName in this._clusterMap) { const cluster = this._clusterMap[appName]; for (let i = 0; i < cluster.length; i++) { cluster[i].rmc.disconnect(); } } //关闭服务器 this._server.close((error) => { this._logger.info('ssls', this._context.ssls, this._context.host, this._context.port, 'was closed.'); if (callback) callback(error); }); } /** * 周期循环 */ _onServerLifeCycle() { let totalSocket = 0; let totalSession = 0; for (let id in this._socketMap) { const session = this._socketMap[id]; if (session.isExpired(this._config.timeout)) { session.close(RouteCode.CODE_TIMEOUT.code, RouteCode.CODE_TIMEOUT.data); //清除超时的链接 } else { totalSocket += 1; totalSession += session.isBinded() ? 1 : 0; } } this._logger.info('_onServerLifeCycle:', 'totalSocket->', totalSocket, 'totalSession->', totalSession); //更新连接数量 this._totalSocket = totalSocket; this._totalSession = totalSession; //回调上层绑定的监听器 if (this._serverCyclerListener) { this._serverCyclerListener(this, this._totalSocket, this._totalSession); } } /** * 收到连接后注册监听 * @param socket * @param request */ _onWebSocketConnection(socket, request) { const session = new WssSession_1.WssSession(socket, this._context.getIPV4({ headers: request.headers, ip: request.socket.remoteAddress })); this._socketMap[session.id] = session; //绑定到_socketMap socket.binaryType = 'arraybuffer'; //指定读取格式为arraybuffer socket.on('message', (data) => { this._onWebSocketMessage(session, data); }); socket.on('close', (code, reason) => { this._logger.info('on websocket close:', session.ip, session.id, session.uid, code, reason); //回调上层绑定的监听器 if (this._sessionCloseListener) { this._sessionCloseListener(this, session, code, reason); } //统一进行内存清理操作 session.eachChannel((gid) => { this.quitChannel(session, gid); }); //退出已加入的所有分组 this.unbindUid(session); //可能已经绑定了uid,需要进行解绑操作 delete this._socketMap[session.id]; //从_socketMap中移除 }); socket.on('error', (error) => { this._logger.error('on websocket error:', session.ip, session.id, session.uid, error.toString()); session.close(RouteCode.CODE_SOCKET.code, RouteCode.CODE_SOCKET.data + ': ' + error.toString()); }); this._logger.info('on websocket connection:', session.ip, session.id); } /** * * @param session * @param data */ _onWebSocketMessage(session, data) { const pack = AllExport_1.WssBridgePackData.deserialize(data, this._config.pwd); //解析包数据 if (!pack) { this._logger.error('_onWebSocketMessage:', session.ip, session.id, session.uid, RouteCode.CODE_PARSE.code, data); session.close(RouteCode.CODE_PARSE.code, RouteCode.CODE_PARSE.data); return; } //校验包格式 if (typeof pack.route !== 'string' || typeof pack.reqId !== 'number' || pack.message === undefined || pack.message === null) { this._logger.error('_onWebSocketMessage:', session.ip, session.id, session.uid, RouteCode.CODE_FORMAT.code, pack); session.close(RouteCode.CODE_FORMAT.code, RouteCode.CODE_FORMAT.data); return; } //校验重复包 if (!session.updateReqId(pack.reqId, this._config.reqIdCache)) { this._logger.error('_onWebSocketMessage:', session.ip, session.id, session.uid, RouteCode.CODE_REPEAT.code, pack); session.close(RouteCode.CODE_REPEAT.code, RouteCode.CODE_REPEAT.data); return; } //收到心跳包 if (pack.route === RouteCode.ROUTE_HEARTICK) { this._logger.trace('_onWebSocketMessage:', session.ip, session.id, session.uid, pack); session.updateHeart(); //更新本次心跳时间戳 this._sendHeartick(session, pack); //按照原样发回客户端 return; } //集群P2P包 if (pack.route === RouteCode.ROUTE_INNERP2P) { if (this._validateInnerData(pack.message)) { this._logger.debug('_onWebSocketMessage:', session.ip, session.id, session.uid, pack); this.pushSession(pack.message.tid, pack.message.route, pack.message.message); } else { this._logger.error('_onWebSocketMessage:', session.ip, session.id, session.uid, RouteCode.CODE_SIGN.code, pack); session.close(RouteCode.CODE_SIGN.code, RouteCode.CODE_SIGN.data); } return; } //集群GRP包 if (pack.route === RouteCode.ROUTE_INNERGRP) { if (this._validateInnerData(pack.message)) { this._logger.debug('_onWebSocketMessage:', session.ip, session.id, session.uid, pack); this.pushChannel(pack.message.tid, pack.message.route, pack.message.message); } else { this._logger.error('_onWebSocketMessage:', session.ip, session.id, session.uid, RouteCode.CODE_SIGN.code, pack); session.close(RouteCode.CODE_SIGN.code, RouteCode.CODE_SIGN.data); } return; } //集群ALL包 if (pack.route === RouteCode.ROUTE_INNERALL) { if (this._validateInnerData(pack.message)) { this._logger.debug('_onWebSocketMessage:', session.ip, session.id, session.uid, pack); this.broadcast(pack.message.route, pack.message.message); } else { this._logger.error('_onWebSocketMessage:', session.ip, session.id, session.uid, RouteCode.CODE_SIGN.code, pack); session.close(RouteCode.CODE_SIGN.code, RouteCode.CODE_SIGN.data); } return; } //集群RMC包 if (pack.route === RouteCode.ROUTE_INNERRMC) { if (this._validateInnerData(pack.message)) { if (this._remoteMap[pack.message.route]) { this._logger.debug('_onWebSocketMessage:', session.ip, session.id, session.uid, pack); this._remoteMap[pack.message.route](this, session, new AllExport_1.WssBridgePackData(pack.message.route, pack.reqId, pack.message.message)); //调用远程方法 } else { this._logger.error('_onWebSocketMessage:', session.ip, session.id, session.uid, RouteCode.CODE_REMOTE.code, pack); session.close(RouteCode.CODE_REMOTE.code, RouteCode.CODE_REMOTE.data); } } else { this._logger.error('_onWebSocketMessage:', session.ip, session.id, session.uid, RouteCode.CODE_SIGN.code, pack); session.close(RouteCode.CODE_SIGN.code, RouteCode.CODE_SIGN.data); } return; } //自定义路由 if (this._routerMap[pack.route]) { this._logger.debug('_onWebSocketMessage:', session.ip, session.id, session.uid, pack); this._routerMap[pack.route](this, session, pack); //调用路由方法 return; } //没找到路由 this._logger.error('_onWebSocketMessage:', session.ip, session.id, session.uid, RouteCode.CODE_ROUTE.code, pack); session.close(RouteCode.CODE_ROUTE.code, RouteCode.CODE_ROUTE.data); } /** * 返回发送数据到客户端websocket的选项 */ _getSendOptions() { return { binary: this._config.binary }; } /** * 响应心跳包 * @param session * @param reqPack */ _sendHeartick(session, reqPack) { const pack = new AllExport_1.WssBridgePackData(RouteCode.ROUTE_HEARTICK, reqPack.reqId, reqPack.message); const data = AllExport_1.WssBridgePackData.serialize(pack, this._config.pwd, this._config.binary); session.send(data, this._getSendOptions()); this._logger.trace('_sendHeartick:', session.ip, session.id, session.uid, pack); } /** * 连接到集群节点 * @param node */ _connectForCluster(node) { node.rmc.setLogLevel(AllExport_1.WssBridge.LOG_LEVEL_NONE); node.rmc.connect(() => { this._logger.mark('cluster onopen->', node.grp, node.url); }, (code, reason, params) => { // this._logger.warn('cluster onclose->', code, reason); }, (error, params) => { // this._logger.error('cluster onerror->', error); }, (count, params) => { this._logger.debug('cluster onretry->', node.grp, node.url, count, 'times'); }, null, this); } /** * 生成内部签名数据包 * @param tid * @param route * @param message */ _generateInnerData(tid, route, message) { const data = {}; if (tid) data.tid = tid; data.route = route; data.message = message; data.word = uuid_1.v1(); data.sign = this._context.getMd5(route + data.word + this._config.secret); return data; } /** * 校验内部签名数据包 * @param data */ _validateInnerData(data) { return this._context.getMd5(data.route + data.word + this._config.secret) === data.sign; } /** * 返回Logger实例 */ get logger() { return this._logger; } get wssapp() { return this._wssapp; } get server() { return this._server; } get wssPwd() { return this._config.pwd; } get wssSecret() { return this._config.secret; } } exports.WssServer = WssServer; /** * 状态码范围参考: https://tools.ietf.org/html/rfc6455#section-7.4.2 * 以及:https://github.com/websockets/ws/issues/715 */ class RouteCode { } /** * 路由 */ RouteCode.ROUTE_HEARTICK = '$heartick$'; //心跳包路由 RouteCode.ROUTE_RESPONSE = '$response$'; //响应请求路由 RouteCode.ROUTE_INNERP2P = '$innerP2P$'; //集群点对点消息路由 RouteCode.ROUTE_INNERGRP = '$innerGRP'; //集群分组消息路由 RouteCode.ROUTE_INNERALL = '$innerALL$'; //集群广播消息路由 RouteCode.ROUTE_INNERRMC = '$innerRMC$'; //集群远程方法路由 /** * 状态 * 本框架保留状态码: * 4001-4100 服务端保留状态码范围 * 4101-4200 客户端保留状态码范围 * 4201-4999 可自定义的状态码范围 */ RouteCode.CODE_PARSE = { code: 4001, data: 'parse error' }; RouteCode.CODE_FORMAT = { code: 4002, data: 'format error' }; RouteCode.CODE_REPEAT = { code: 4003, data: 'repeat error' }; RouteCode.CODE_SIGN = { code: 4004, data: 'sign error' }; RouteCode.CODE_REMOTE = { code: 4005, data: 'remote error' }; RouteCode.CODE_ROUTE = { code: 4006, data: 'route error' }; RouteCode.CODE_SOCKET = { code: 4007, data: 'socket error' }; RouteCode.CODE_TIMEOUT = { code: 4008, data: 'timeout error' }; RouteCode.CODE_NEWBIND = { code: 4009, data: 'newbind error' };