UNPKG

zero-cluster

Version:

分布式游戏服务框架

1,000 lines (999 loc) 36.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ClusterVisitor = exports.Connector = exports.Module = exports.PlayerInfo = exports.BridgeServer = exports.ModuleServer = exports.ConnectorVisitor = exports.ConnectorServer = exports.ZeroServerPlayer = exports.ClientFotModule = exports.ZeroServer = exports.ServerType = exports.getRoute = exports.balance = exports.route = exports.balancePool = exports.routeMap = void 0; const ws_1 = __importDefault(require("ws")); const ip_1 = require("ip"); const zero_remote_1 = __importStar(require("zero-remote")); const ZeroRemoteClient_1 = require("zero-remote/ZeroRemoteClient"); const zero_dispatcher_1 = __importDefault(require("zero-dispatcher")); exports.routeMap = new WeakMap(); exports.balancePool = {}; /** * 装饰器 用于路由接口 * @param key * @returns */ function route(key) { return (type) => { exports.routeMap.set(type, key); }; } exports.route = route; /** * 装饰器 用于落点均衡 * @param callback * @returns */ function balance(callback) { return (type) => { let route = getRoute(type); if (route) { exports.balancePool[route] = callback; } }; } exports.balance = balance; /** * 获取装饰器路由名 * @param connectorType * @returns */ function getRoute(connectorType) { let route = exports.routeMap.get(connectorType); if (route) { return route; } else { throw new Error("这个类没有@route"); } } exports.getRoute = getRoute; /** * 服务节点类型 */ var ServerType; (function (ServerType) { ServerType[ServerType["MODULE"] = 0] = "MODULE"; ServerType[ServerType["CONNECT"] = 1] = "CONNECT"; ServerType[ServerType["SPECIAL"] = 2] = "SPECIAL"; })(ServerType = exports.ServerType || (exports.ServerType = {})); /** * 一个服务节点基类 * 一个服务节点实例拥有一个独立进程 内存 访问地址 * 服务节点实例之间使用长连接通信 * 服务节点可以有多个实例 * 服务节点的子类应该全部支持动态扩容 * 所有的服务节点都会向主服务进行连接并注册自己 */ class ZeroServer extends ZeroRemoteClient_1.ZeroRemoteClient { constructor(port) { super(ZeroServer.mainUrl); this.port = port; this.serverType = ServerType.MODULE; this.modules = []; this.clusterSender = this.getSender(""); this.clusterReceiver = this.getReceiver(""); this.serverPool = {}; this.key = ""; this.isClustered = false; this.relinkMaxCount = 1800; this.label = ""; this.info = {}; this.name = this.constructor.name; if (ZeroServer.mainUrl == null) { throw "请配置主服务器地址ZeroServer.mainUrl=\"XXX\""; } /** * 保存所有服务节点以方便扩展 */ this.clusterReceiver.add((info) => { this.serverPool[info.sid] = info; }); this.clusterReceiver.remove((sid) => { delete this.serverPool[sid]; }); this.clusterReceiver.clustered((sid) => { if (this.serverId == null) { this.isClustered = true; this.serverId = sid; this.clustered(); } else { this.serverPool = {}; console.log("重连集群"); } }); this.clusterReceiver.upInfo((value) => { if (this.serverPool[value.sid] != null) { this.serverPool[value.sid].info[value.key] = value.value; } }); this.link(); this.on("linked", () => { if (ZeroServer.pid == null) { this.clusterSender.getPid().then((pid) => { if (ZeroServer.pid == null) { ZeroServer.pid = pid; } this.sendInit(); }); } else { this.sendInit(); } }); /** * 生命不息 重连不止 */ this.on("popup", () => { console.log("集群失败"); }); } clustered() { } sendInit() { this.clusterSender.init({ url: ip_1.address() + ":" + this.port, name: this.name, label: this.label, key: this.key, type: this.serverType, modules: this.modules, pid: ZeroServer.pid, info: this.info }).catch((e) => { console.log("注册服务节点出错", e); }); } upInfo(key, value) { this.clusterSender.upInfo({ key, value }).then(() => { }).catch((e) => { console.log("同步服务节点信息出错upInfo", e); }); } addModule(moduleType) { let route = getRoute(moduleType); if (route) { let module = new moduleType(route); module.server = this; module.start(); ZeroServer.modulePool[route] = module; this.modules.push(route); return module; } } } exports.ZeroServer = ZeroServer; ZeroServer.modulePool = {}; /** * 启动在连接服务节点上 * 用来连接模块的线路 */ class ClientFotModule extends ZeroRemoteClient_1.ZeroRemoteClient { constructor(url) { super(url); this.url = url; this.relinkMaxCount = 100; this.popupReLinkTime = 1000; } initConnector(sid) { this.send("connector", "init", [sid], (ok, err) => { }); } } exports.ClientFotModule = ClientFotModule; /** * 用户 * 一个用户一个实例 * 在连接服务节点上的一个用户实例 */ class ZeroServerPlayer extends zero_remote_1.GameVisitor { constructor(channel, server, info) { super(channel, server); this.info = info; this.hook = new zero_dispatcher_1.default(); this.connectorPool = {}; ZeroServerPlayer.pool[info.vid] = this; ZeroServerPlayer.count++; } static getAtuoId() { return this.index++; } clear() { super.clear(); if (ZeroServerPlayer.pool[this.info.vid] != null) { delete ZeroServerPlayer.pool[this.info.vid]; ZeroServerPlayer.count--; for (const key in this.connectorPool) { const item = this.connectorPool[key]; item.isClear = true; item.clear(); } } this.hook.emit("clear"); } } exports.ZeroServerPlayer = ZeroServerPlayer; ZeroServerPlayer.pool = {}; ZeroServerPlayer.index = 0; ZeroServerPlayer.count = 0; /** * 连接服务节点 * 连接服务节点面对用户 * 连接服务节点 可注册 连接器 和模块 * 连接服务对用户提供长连接服务 */ class ConnectorServer extends ZeroServer { constructor(port, playerInfoType) { super(port); this.playerInfoType = playerInfoType; this.connectorTypePool = {}; this.serverType = ServerType.CONNECT; this.modulesChannels = {}; this.clients = {}; this.bridges = new Set(); this.lineSet = new Set(); this.bridgesEntries = this.bridges.entries(); } getBridge() { let nextInfo = this.bridgesEntries.next(); if (nextInfo.done == true) { this.bridgesEntries = this.bridges.entries(); nextInfo = this.bridgesEntries.next(); } return nextInfo.value[0]; } userReceive(connector, route, key, args) { } userReject(connector, err, request) { if (err instanceof Error) { console.error(err.stack); } else { console.log(err); } } clustered() { console.log("创建连接服务:", this.serverId, ip_1.address() + ":" + this.port); new zero_remote_1.default(new ws_1.default.Server({ port: this.port }), (channel, server) => { let user = new ZeroServerPlayer(channel, server, new this.playerInfoType(this.serverId, ZeroServerPlayer.getAtuoId())); for (const key in this.connectorTypePool) { if (Object.prototype.hasOwnProperty.call(this.connectorTypePool, key)) { const element = this.connectorTypePool[key]; let connector = new element(this, user, key); user.connectorPool[key] = connector; } } for (const key in user.connectorPool) { if (Object.prototype.hasOwnProperty.call(user.connectorPool, key)) { const element = user.connectorPool[key]; element.start(); } } return user; }); this.clusterReceiver.add((info) => { if (info.type == ServerType.MODULE) { console.log(`发现服务:${info.name} 地址:${info.url}`); let clientFotModule = this.clients[info.sid]; if (clientFotModule == null) { clientFotModule = this.createClient(info); this.clients[info.sid] = clientFotModule; if (info.key == "bridge") { this.bridges.add(clientFotModule); } info.modules.forEach((modulesName) => { let clinets = this.modulesChannels[modulesName]; if (clinets == null) { clinets = []; this.modulesChannels[modulesName] = clinets; } clinets.push(clientFotModule); if (clientFotModule.label != null && clientFotModule.label != "") { clinets[clientFotModule.label] = clientFotModule; } }); } } }); this.clusterReceiver.remove((sid) => { let client = this.clients[sid]; if (client != null) { client.close(); delete this.clients[sid]; if (this.bridges.has(client)) { this.bridges.delete(client); } for (const key in this.modulesChannels) { if (Object.prototype.hasOwnProperty.call(this.modulesChannels, key)) { const element = this.modulesChannels[key]; let newList = []; element.forEach((item) => { if (item.url != client.url) { newList.push(item); if (item.label != null && item.label != "") { newList[item.label] = item; } } }); this.modulesChannels[key] = newList; } } } }); } createClient(info) { let clientFotModule = new ClientFotModule("ws://" + info.url); clientFotModule.label = info.label; clientFotModule.link(() => { clientFotModule.initConnector(this.serverId); }); clientFotModule.on("suspend", () => { this.lineSet.delete(info.sid); this.upInfo("lines", [...this.lineSet]); console.log("中断连接服务模块"); }); clientFotModule.on("linked", () => { this.lineSet.add(info.sid); this.upInfo("lines", [...this.lineSet]); console.log("连接服务模块成功"); }); clientFotModule.on("stop", () => { console.log("中止连接服务模块"); }); clientFotModule.receive("module", "", this.moduleMessage.bind(this)); clientFotModule.receive("module", "kick", this.moduleKickMessage.bind(this)); clientFotModule.receive("router", "", this.serverMessage.bind(this)); return clientFotModule; } /** * 转发到链接路由 * @param data */ serverMessage(data) { this.router.emit("router." + data.key, data.data); } /** * 转发到前端 * @param data */ moduleMessage(data) { let user = ZeroServerPlayer.pool[data.vid]; if (user) { user.channel.send({ route: data.route, key: data.key, data: data.data }); } } /** * 转发到前端 * @param data */ moduleKickMessage(data) { let user = ZeroServerPlayer.pool[data.vid]; if (user) { user.kick(data.data); } } addConnector(connectorType) { let route = getRoute(connectorType); if (route) { this.connectorTypePool[route] = connectorType; } } } exports.ConnectorServer = ConnectorServer; /** * 远程的连接服务节点访客 * 启动在模块服务节点上,用于处理 模块服务节点 到 连接服务节点 的逻辑 * 有个连接服务点节作为客户端连了上来 */ class ConnectorVisitor extends zero_remote_1.GameVisitor { constructor(channel, moduleServer) { super(channel, moduleServer.connectorServer); this.moduleServer = moduleServer; this.getReceiver("connector").init((serverId) => __awaiter(this, void 0, void 0, function* () { this.serverId = serverId; this.moduleServer.connectorVisitorPool[serverId] = this; this.moduleServer.upLine(); console.log("游戏前台连接初始化", this.serverId); })); } clear() { super.clear(); if (this.serverId != null) { delete this.moduleServer.connectorVisitorPool[this.serverId]; } console.log("游戏前台断开", Object.keys(this.moduleServer.connectorVisitorPool)); this.moduleServer.upLine(); } sendRouterInfo(key, data) { let sendInfo = { route: "router", key: "", data: { key: key, data: data, } }; this.channel.send(sendInfo); } handle(route, key, data, back) { if (route == "module") { let module = ZeroServer.modulePool[data.r]; let method = module[data.k]; if (method) { module.playerInfo = data.p; module.isRemote = true; let p = method.apply(module, data.a); if (back != null) { if (p instanceof Promise) { p.then((valueThen) => { back(true, valueThen); }).catch((error) => { error.message; if (typeof error == "string") { back(false, error); } if (error instanceof Error) { if (error.message == null) { back(false, JSON.parse(JSON.stringify(error))); } else { console.error(error); back(false, error.message); } } else { back(false, error); } }); } else { back(true, p); } } } } else { super.handle(route, key, data, back); } } } exports.ConnectorVisitor = ConnectorVisitor; /** * 模块服务节点 * 模块服务节点只能注册模块 * 模块服务节点 接受来自所有的 连接服务节点 的连接请求 用于对连接服务节点提供服务 * 模块服务节点为被动节点 */ class ModuleServer extends ZeroServer { constructor(port) { super(port); this.serverType = ServerType.MODULE; this.connectorVisitorPool = {}; this.connectorServer = new zero_remote_1.default(new ws_1.default.Server({ port: this.port }), this.createVisitor.bind(this)); } createVisitor(channel, server) { return new ConnectorVisitor(channel, this); } clustered() { console.log(`创建服务${this.name} :${this.serverId}`); } getLines() { let out = []; for (const key in this.connectorVisitorPool) { out.push(key); } return out; } /**报告给集群 */ upLine() { this.clusterSender.upInfo({ key: "lines", value: this.getLines() }).then(() => { }).catch((e) => { console.log("集群同步个体信息出错upLine", e); }); } } exports.ModuleServer = ModuleServer; /** * 桥接服务是一种模块服务节点 * 桥接服务是一种已实现逻辑的特殊的模块服务节点 * 启用桥接服务后 可以在模块中给其它服务节点实例的用户发送信息 * 桥接服务 对所有的 连接服务节点 起作点 * 桥接服务启动多个实例后 将轮流处理每个请求 (可优化成按权重分配,需要让桥接服务向主服务发送自己的的权重值) */ class BridgeServer extends ModuleServer { constructor() { super(...arguments); this.name = "桥接服务"; this.key = "bridge"; } createVisitor(channel, server) { let v = new ConnectorVisitor(channel, this); v.getReceiver("connector").bridge((data) => __awaiter(this, void 0, void 0, function* () { let server = this.connectorVisitorPool[data.playerInfo.serverId]; if (server != null) { server.channel.send(data.data); } else { console.warn("no server " + data.playerInfo.serverId); } })); return v; } } exports.BridgeServer = BridgeServer; /** * 被序列化的传递的用户信息 */ class PlayerInfo { /** * * @param serverId * @param vid 用于路由到用户不发给前端 访客ID 内存ID */ constructor(serverId, vid, name) { this.serverId = serverId; this.vid = vid; this.name = name || ("S" + serverId + "U" + vid); } } exports.PlayerInfo = PlayerInfo; /** * 模块 * * 模块是单例模块 * 所有用户共用模块 * 模块被调用时含有基础用户信息 用户信息会被序列化和反序列化处理过 * 基础用户信息格式是所有模块统一格式 * 基础用户信息中应包含可用于检索更多用户信息的检索ID * 模块与后端到前端通信接口一个模块组一一对应 * 模块可向同一服务节点实例用户直接返回信息 * 向不同服务节点实例的用户返回信息需要启动桥接服务 * 模块之间无法互相调用 !!! * */ class Module { constructor(route) { this.route = route; this.isRemote = false; this.moduleKey = "module"; this.sender = new Proxy({}, { get: (target, p, receiver) => { return (data, playerInfo = null) => { if (playerInfo == null) { playerInfo = this.playerInfo; } if (playerInfo) { if (this.isRemote) { let sendInfo = { route: this.moduleKey, key: "", data: { route: this.route, key: p, data: data, vid: playerInfo.vid, time: new Date().getTime() } }; if (this.server instanceof ModuleServer) { let visitor = this.server.connectorVisitorPool[playerInfo.serverId]; if (visitor != null) { visitor.channel.send(sendInfo); } else { console.log(">>>>用户所在服务器可能出现问题", sendInfo); } } else { console.log(">>>>这个情况不明"); } } else { if (playerInfo.serverId != this.playerInfo.serverId) { //这里要跨节通信 let sendInfo = { route: this.moduleKey, key: "", data: { route: this.route, key: p, data: data, vid: playerInfo.vid, time: new Date().getTime() } }; let bridge = this.server.getBridge(); bridge.send("connector", "bridge", { playerInfo: playerInfo, data: sendInfo }, () => { }); } else { let sendInfo = { route: route, key: p, data: data, time: new Date().getTime() }; let user = ZeroServerPlayer.pool[playerInfo.vid]; if (user != null) { user.channel.send(sendInfo); } else { console.warn("no User"); } } } } }; } }); } start() { } kick(playerInfo = null, data = 0) { if (playerInfo == null) { playerInfo = this.playerInfo; } let sendInfo = { route: this.moduleKey, key: "kick", data: data }; let visitor = this.server.connectorVisitorPool[playerInfo.serverId]; visitor.channel.send(sendInfo); } } exports.Module = Module; /** * 连接器 * 连接器面对用户 * 用户断线则连接器失效 * 连接器与前端到后端通信接口一个模块组一一对应 * 连接器用来处理一个前端通信接口的一个模块组下的所有接口 * 每一个用户每个模块都会产一个有连接器实例 * 连接器可以调用 服务模块 自动识别 服务横块是远程服务还是本机调用 * 调用注册在不同服务节点的模块时识别为远程调用 * 处理玩家的请求做了队列处理 * 可以获取注册在同一服务节点下的其它连接器的实例 */ class Connector { constructor(server, player, route) { this.server = server; this.route = route; this.maxLink = 100; this.isFree = true; this.isClear = false; this.requestQueue = []; this.moduleKey = "module"; this.player = player; this.route = route; /** * 接收来自己玩家的请求所有做了队例处理 防攻击 */ this.receiver = new Proxy({}, { get: (target, p, receiver) => { return (callback) => { let router = this.player.routerPool[route]; if (router == null) { router = {}; this.player.routerPool[route] = router; } router[p] = (args) => { return new Promise((resolve, reject) => { if (this.requestQueue.length >= this.maxLink) { reject("请求数超出数量"); } else { this.requestQueue.push({ route, key: p, callback, args, resolve, reject, }); } if (this.isFree) { this.isFree = false; this.nextReceiver(); } }); }; }; } }); } start() { } nextReceiver() { if (!this.isClear) { if (this.requestQueue.length > 0) { let request = this.requestQueue.shift(); if (request != null && request.callback != null) { request.callback.apply(null, request.args).then((value) => { //TODO 事务成功 request.resolve(value); this.server.userReceive(this, request.route, request.key, request.args); this.nextReceiver(); }).catch((err) => { //TODO 事务失败 request.reject(err); // 错误事件 this.server.userReject(this, err, request); this.nextReceiver(); }); } } else { this.isFree = true; } } } getConnector(connectorType) { let routeKey = getRoute(connectorType); let connector = this.player.connectorPool[routeKey]; return connector; } getModule(moduleType) { let routeKey = getRoute(moduleType); let module = ZeroServer.modulePool[routeKey]; if (module == null) { let remoteModule = this.getRemoteModule(routeKey); return new Proxy({}, { get: (target, p, receiver) => { if (typeof moduleType.prototype[p] == "function") { return (...args) => { return new Promise((resolve, reject) => { if (remoteModule == null || remoteModule.isOpen == false) { remoteModule = this.getRemoteModule(routeKey); } if (remoteModule != null) { if (remoteModule.isOpen) { remoteModule.send(this.moduleKey, "", { r: routeKey, k: p, p: this.player.info, a: args }, (isError, value) => { if (isError) { resolve(value); } else { console.log("========================>远程回调出错", routeKey, p, value); reject(value); } }); } else { reject("服务关闭"); } } else { reject("没有这个服务 " + routeKey); console.log("没有这个服务" + routeKey + " " + p); } }); }; } else { //TODO } }, set: (target, p, value, receiver) => { console.log(p); return true; } }); } else { return new Proxy({}, { get: (target, p, receiver) => { if (typeof moduleType.prototype[p] == "function") { return (...args) => { this.isFree; module.playerInfo = this.player.info; let _module = module; return _module[p].apply(module, args); }; } else { console.error("不支持属性调用"); } }, set: (target, p, value, receiver) => { console.error("不支持属性赋值"); console.log(p); return true; } }); } } getRemoteModule(routeKey) { let remoteModules = this.server.modulesChannels[routeKey]; if (remoteModules) { if (exports.balancePool[routeKey]) { let pick = exports.balancePool[routeKey](this.player, remoteModules); return remoteModules[pick]; } else { console.error("请添加@balance"); return null; } } else { return null; } } clear() { } } exports.Connector = Connector; /** * 集群管理主服务 * 只用于发现服务节点 * 没有任何服务逻辑 * 责任广播服务节点的注删信息连接信息通信时间等服务节点的基本状态信息 */ class ZeroCluster extends zero_remote_1.default { constructor(port) { super(new ws_1.default.Server({ port: port }), (channel, server) => { return new ClusterVisitor(channel, server); }); this.port = port; } getAddress(value) { return value + ip_1.address() + ":" + this.port.toString(); } } exports.default = ZeroCluster; class ClusterVisitor extends zero_remote_1.GameVisitor { constructor() { super(...arguments); this.receiver = this.getReceiver(""); this.sender = this.getSender(""); this.key = ""; this.info = {}; } start() { this.clusterServer = this.server; this.receiver.init((info) => __awaiter(this, void 0, void 0, function* () { //TODO 这里要加密 let server = ClusterVisitor.pool[info.url]; if (server != null) { server.clear(); delete ClusterVisitor.pool[info.url]; } let sid = ClusterVisitor.sidPool[info.url]; if (sid != null) { this.sid = sid; console.log("加入旧节点"); } else { this.sid = ClusterVisitor.serverIndex; ClusterVisitor.sidPool[info.url] = this.sid; ClusterVisitor.serverIndex++; console.log("加入新节点", info.name, info.url); } this.url = info.url; this.modules = info.modules; this.type = info.type; this.pid = info.pid; this.name = info.name; this.key = info.key; this.label = info.label; this.info = info.info; /** * 给自己发一个完成的信息 */ this.sender.clustered(this.sid); for (const key in ClusterVisitor.pool) { const element = ClusterVisitor.pool[key]; /** * 向新入加者发送已加入者信息 */ this.sender.add({ url: element.url, modules: element.modules, type: element.type, sid: element.sid, name: element.name, key: element.key, label: element.label, info: element.info }); /** * 向所有已加入者发送新入加者信息 */ element.sender.add({ url: info.url, modules: info.modules, type: info.type, name: info.name, key: info.key, sid: this.sid, label: info.label, info: info.info }); } ClusterVisitor.pool[info.url] = this; })); this.receiver.getPid(() => __awaiter(this, void 0, void 0, function* () { return ClusterVisitor.serverPid++; })); this.receiver.upInfo((value) => __awaiter(this, void 0, void 0, function* () { for (const subKey in ClusterVisitor.pool) { const element = ClusterVisitor.pool[subKey]; element.sender.upInfo({ sid: this.sid, key: value.key, value: value.value }); } })); this.receiver.getAllPingTime(() => __awaiter(this, void 0, void 0, function* () { let value = {}; for (const key in ClusterVisitor.pool) { const element = ClusterVisitor.pool[key]; value[element.sid] = element.pingTime; } return value; })); } clear() { super.clear(); delete ClusterVisitor.pool[this.url]; for (const key in ClusterVisitor.pool) { if (Object.prototype.hasOwnProperty.call(ClusterVisitor.pool, key)) { const element = ClusterVisitor.pool[key]; element.sender.remove(this.sid); } } } } exports.ClusterVisitor = ClusterVisitor; ClusterVisitor.serverIndex = 0; ClusterVisitor.serverPid = 0; ClusterVisitor.pool = {}; ClusterVisitor.sidPool = {};