egretgit
pomelogithub
pomelo撿寶項目web
1.下載並搭建pomelo項目json
2.下載pomelo撿寶項目(github上下載的,最好是看一遍git上的教程,再進行搭建會比較順利)api
3.下載的撿寶項目[Treasures] 中有簡略的項目教程,能夠幫助咱們快速搭建和熟悉撿寶項目。服務器
因我的比較熟悉egret引擎,在論壇中找到 egret pomelo的第三方庫app
使用egret wing 建立遊戲項目,在項目src目錄下,建立network文件夾,在文件夾下新建PomeloSocket類用來連接Pomelo服務端
1 module network { 2 /** 3 * 連接pomelo服務端 4 */ 5 export class PomeloSocket { 6 public constructor() { 7 } 8 9 private pomelo: Pomelo; 10 /** 11 * 當前正在操做的是服務端 12 */ 13 private currServer: network.PomeloService; 14 /** 15 * 服務端狀態 是否開啓 16 */ 17 private running: boolean = false; 18 19 init() { 20 if (this.pomelo == null) { 21 this.pomelo = new Pomelo(); 22 23 this.pomelo.on('server_push_message', (msg) => { 24 var route = msg["route"]; 25 //根據服務端返回派發事件 26 { 27 switch (route) { 28 case "addEntities": 29 Global.dispatchEvent(events.PomeloServerEvents.ADDENTITIES, msg); 30 break; 31 case "rankUpdate": 32 Global.dispatchEvent(events.PomeloServerEvents.RANKUPDATE, msg); 33 break; 34 case "onUserLeave": 35 Global.dispatchEvent(events.PomeloServerEvents.USERLEAVE, msg); 36 break; 37 case "removeEntities": 38 Global.dispatchEvent(events.PomeloServerEvents.REMOVEENTITIES, msg); 39 break; 40 case "onMove": 41 Global.dispatchEvent(events.PomeloServerEvents.ENTITYMOVE, msg); 42 break; 43 case "onChangeStage": 44 Global.dispatchEvent(events.PomeloServerEvents.STAGECHANGE, msg); 45 break; 46 default: 47 trace("收到新的須要處理的事件~~~~~~~~~~~~~~待處理信息爲:"); 48 trace(msg); 49 break; 50 } 51 } 52 }); 53 54 this.pomelo.on('onKick', (msg) => { 55 trace("onKick"); 56 }); 57 58 this.pomelo.on('heartbeat_timeout', () => { 59 trace("heartbeat_timeout"); 60 }); 61 62 this.pomelo.on('close', (e: CloseEvent) => { 63 trace(e.currentTarget["url"] + "的連接被斷開"); 64 }); 65 } 66 } 67 68 /** 69 * 打開服務端 70 * @param serverType:服務端類型 71 * @param host:ip 72 * @param port:端口 73 * @param callback:回調函數 74 * @param log:是否啓用日誌 75 */ 76 open(serverType: network.PomeloService, host: string, port: number, callback?: Function, log: boolean = true) { 77 this.pomelo.init({ host: host, port: port, log: log }, false, (succeedRes) => { 78 this.currServer = serverType; 79 this.running = true; 80 switch (serverType) { 81 case network.PomeloService.GATE: 82 Global.dispatchEvent(events.PomeloServerEvents.CONNECTION_GATE_SUCCEED); 83 break; 84 case network.PomeloService.CONNECTION: 85 Global.dispatchEvent(events.PomeloServerEvents.CONNECTION_CONNECT_SUCCEED); 86 break; 87 default: 88 trace("========================試圖打開程序中未知服務器,請求被拒絕========================================="); 89 break; 90 } 91 }, (errRES) => { 92 switch (serverType) { 93 case network.PomeloService.GATE: 94 Global.dispatchEvent(events.PomeloServerEvents.CONNECTION_GATE_ERROR); 95 break; 96 case network.PomeloService.CONNECTION: 97 Global.dispatchEvent(events.PomeloServerEvents.CONNECTION_CONNECT_ERROR); 98 break; 99 default: 100 trace("========================試圖打開程序中未知服務器,請求被拒絕========================================="); 101 break; 102 } 103 }, (closeRes) => { 104 trace("一個服務端關閉完成。"); 105 }, null); 106 } 107 108 /** 109 * 發起請求 110 * @param route: 路由 (服務端處理函數) 111 * @param msg:內容 112 * @param callback:回調函數 113 * @param thisArg:參數 114 */ 115 request(route: string, msg: any, callback: Function, thisArg?: any): void { 116 this.pomelo.request(route, msg, (response) => { 117 callback.call(thisArg, response); 118 }); 119 } 120 121 /** 122 * 通知 123 */ 124 notify(route: string, msg: any): void { 125 this.pomelo.notify(route, msg); 126 } 127 128 /** 129 * 關閉當前服務 130 */ 131 disconnect() { 132 this.pomelo.disconnect(); 133 this.running = false; 134 Global.dispatchEvent(events.PomeloServerEvents.DISCONNECT_SUCCEED, { currServer: this.currServer }); 135 } 136 137 /** 138 * 獲取當前的服務端 139 */ 140 getCurrServer(): PomeloService { 141 return this.currServer; 142 } 143 /** 144 * 獲取當前的服務端狀態 145 */ 146 isRunning(): boolean { 147 return this.running; 148 } 149 } 150 }
在文件夾下新建PomeloService類用來連接Pomelo服務端
1 module network { 2 /** 3 * 服務端模塊列表 4 */ 5 export class PomeloService { 6 public constructor() { 7 } 8 /** 9 * Gate模塊 10 */ 11 public static GATE: string = "PomeloService_GATE"; 12 /** 13 * Connect 模塊操做 14 */ 15 public static CONNECTION: string = "PomeloService_CONNECTION"; 16 } 17 }
在項目src目錄下建立pomeloTest文件,連接pomelo相應的服務端
class PomeloTest { private connectIp: string; private connectPort: number; public constructor() { Global.addEventListener(events.PomeloServerEvents.CONNECTION_GATE_SUCCEED, this.onGateSucceed, this); Global.addEventListener(events.PomeloServerEvents.CONNECTION_GATE_ERROR, this.onGateError, this); Global.addEventListener(events.PomeloServerEvents.CONNECTION_CONNECT_SUCCEED, this.onConnectSucceed, this); Global.addEventListener(events.PomeloServerEvents.CONNECTION_CONNECT_ERROR, this.onConnectError, this); } connectGate() { config.Config.pomelo.init(); config.Config.pomelo.open(network.PomeloService.GATE, config.Config.gateServer.ip, config.Config.gateServer.port); } private onGateSucceed() { Global.addEventListener(events.PomeloServerEvents.DISCONNECT_SUCCEED, this.onGateClosed, this); config.Config.pomelo.request("gate.gateHandler.queryEntry", { uid: config.Config.player.name }, this.onGateMsg); trace("Gate服務端連接成功"); } private onGateError() { trace("Gate服務端連接失敗"); } private onGateMsg(gate_data) { this.connectIp = gate_data.host; this.connectPort = gate_data.port; config.Config.pomelo.disconnect(); trace("正在嘗試連接connect服務端..."); config.Config.pomelo.open(network.PomeloService.CONNECTION, this.connectIp, this.connectPort); } private onGateClosed() { trace("Gate服務端成功斷開連接"); // trace("正在嘗試連接connect服務端..."); // config.global.pomelo.open(network.PomeloService.CONNECTION, this.connectIp, this.connectPort); } private onConnectSucceed() { trace("CONNECT服務端連接成功"); trace("開始註冊服務端信息..."); config.Config.pomelo.request('connector.entryHandler.entry', { name: config.Config.player.name }, this.onEntryMsg); } private onConnectError() { trace("CONNECT服務端連接失敗..."); } private onEntryMsg(entry_data) { if (entry_data.code === 200) { trace("註冊信息成功"); trace("開始申請進入遊戲..."); config.Config.pomelo.request('area.playerHandler.enterScene', { name: config.Config.player.name, playerId: entry_data.playerId }, (respose) => { Global.dispatchEvent(events.PomeloServerEvents.MAPMSG, respose); trace("進入遊戲成功"); trace("開始解析地圖信息"); }); } else { trace("註冊服務端信息出現問題,請檢查提交信息"); } } move(x: number, y: number, targetId: string) { config.Config.pomelo.notify('area.playerHandler.move', { targetPos: { x: x, y: y }, target: targetId }); } changeStage(s: string) { config.Config.pomelo.notify('area.playerHandler.changeStage', { S: s }); } }
以上步驟都是準備工做,各語言間的連接方式和代碼都不相同,若是沒有使用egret可使用pomelo項目中自帶的web-server項目就能夠輕鬆搭建起來的,接下來,就是服務端中的代碼說明,由於本人的代碼寫的並非很好,因此既然想作個好點的遊戲,一步一步剖析pomelo的運行方式是很重要的一步。
2.1代碼執行流程
在game-server文件夾下 直接使用pomelo命令啓動app.js
pomelo start
出現這樣的界面,就證實pomelo服務端啓動成功了。那首先的一步 就是查看app文件中的代碼
var bearcat = require('bearcat'); var pomelo = require('pomelo'); /** * Init app for client. */ var app = pomelo.createApp();
當代碼執行到 var app = pomelo.createApp(); 這句時,將執行game-server/node_modules/pomelo/pomelo.js 文件中的 createApp方法
var application = require('./application');
/** * Create an pomelo application. * * @return {Application} * @memberOf Pomelo * @api public */ Pomelo.createApp = function (opts) { var app = application;
//初始化 app.init(opts); self.app = app; return app;
};
這個方法中一共四行執行代碼,第一行是引用了pomelo.js同級目錄下的application.js文件,對pomelo文件中的application對象進行初始化,並將初始化的對方返回給調用該方法的app.js中的app對象。下面咱們看一下,這個app.init(opts);這句話具體作了些什麼呢? 咱們進入application文件看一下。
前面標紅的地方,是對當前application文件中的信息進行一個初始化,由於createApp()調用時並未對其傳遞相關的opts參數,因此,這裏涉及到opts變量相關的應該是「undefined」。
appUtil.defaultConfiguration(this);是作什麼的呢?咱們轉到【 var appUtil = require('./util/appUtil');】 game-server/node_modules/pomelo/util/appUtil.js文件查找defaultConfiguration方法。
setupEnv (環境配置)
var setupEnv = function (app, args) { app.set(Constants.RESERVED.ENV, args.env || process.env.NODE_ENV || Constants.RESERVED.ENV_DEV, true); };
app.set方法:(設置配置文件,並返回設置的值)
/** * Assign `setting` to `val`, or return `setting`'s value. * * Example: * * app.set('key1', 'value1'); * app.get('key1'); // 'value1' * app.key1; // undefined * * app.set('key2', 'value2', true); * app.get('key2'); // 'value2' * app.key2; // 'value2' * * @param {String} setting the setting of application * @param {String} val the setting's value * @param {Boolean} attach whether attach the settings to application * @return {Server|Mixed} for chaining, or the setting value * @memberOf Application */ Application.set = function (setting, val, attach) { if (arguments.length === 1) { return this.settings[setting]; } this.settings[setting] = val; if(attach) { this[setting] = val; } return this; };
loadMaster(加載master json文件)
var loadMaster = function (app) { app.loadConfigBaseApp(Constants.RESERVED.MASTER, Constants.FILEPATH.MASTER); app.master = app.get(Constants.RESERVED.MASTER); };
app.loadConfigBaseApp方法:(遞歸方式 加載json配置文件)
/** * Load Configure json file to settings.(support different enviroment directory & compatible for old path) * * @param {String} key environment key * @param {String} val environment value * @param {Boolean} reload whether reload after change default false * @return {Server|Mixed} for chaining, or the setting value * @memberOf Application */ Application.loadConfigBaseApp = function (key, val, reload) { var self = this; var env = this.get(Constants.RESERVED.ENV); var originPath = path.join(Application.getBase(), val); var presentPath = path.join(Application.getBase(), Constants.FILEPATH.CONFIG_DIR, env, path.basename(val)); var realPath; if(fs.existsSync(originPath)) { realPath = originPath; var file = require(originPath); if (file[env]) { file = file[env]; } this.set(key, file); } else if(fs.existsSync(presentPath)) { realPath = presentPath; var pfile = require(presentPath); this.set(key, pfile); } else { logger.error('invalid configuration with file path: %s', key); } if(!!realPath && !!reload) { fs.watch(realPath, function (event, filename) { if(event === 'change') { delete require.cache[require.resolve(realPath)]; self.loadConfigBaseApp(key, val); } }); } };
master 的json文件加載完成了,下一步就是加載server的json文件
/** * Load server info from config/servers.json. */ var loadServers = function (app) { app.loadConfigBaseApp(Constants.RESERVED.SERVERS, Constants.FILEPATH.SERVER); var servers = app.get(Constants.RESERVED.SERVERS); var serverMap = {}, slist, i, l, server; for (var serverType in servers) { slist = servers[serverType]; for (i = 0, l = slist.length; i < l; i++) { server = slist[i]; server.serverType = serverType; if (server[Constants.RESERVED.CLUSTER_COUNT]) { utils.loadCluster(app, server, serverMap); continue; } serverMap[server.id] = server; if (server.wsPort) { logger.warn('wsPort is deprecated, use clientPort in frontend server instead, server: %j', server); } } } app.set(Constants.KEYWORDS.SERVER_MAP, serverMap); };
首先加載server的json文件並存儲於app中,遍歷讀取到的servers的serverType,經過servers[serverType]可獲取到對應的服務端配置組,使用utils.loadCluster(app, server, serverMap);方法操做, game-server/node_modules/pomelo/util/util.js,下面來看一下loadCluster方法是來作什麼的。
/** * Load cluster server. * */ utils.loadCluster = function(app, server, serverMap) { var increaseFields = {}; var host = server.host; var count = parseInt(server[Constants.RESERVED.CLUSTER_COUNT]); var seq = app.clusterSeq[server.serverType]; if(!seq) { seq = 0; app.clusterSeq[server.serverType] = count; } else { app.clusterSeq[server.serverType] = seq + count; } for(var key in server) { var value = server[key].toString(); if(value.indexOf(Constants.RESERVED.CLUSTER_SIGNAL) > 0) { var base = server[key].slice(0, -2); increaseFields[key] = base; } } var clone = function(src) { var rs = {}; for(var key in src) { rs[key] = src[key]; } return rs; }; for(var i=0, l=seq; i<count; i++,l++) { var cserver = clone(server); cserver.id = Constants.RESERVED.CLUSTER_PREFIX + server.serverType + '-' + l; for(var k in increaseFields) { var v = parseInt(increaseFields[k]); cserver[k] = v + i; } serverMap[cserver.id] = cserver; } };
這個方法能夠看出,是用來作集羣間的負載均衡,將設置app中的clusterSeq 屬性值,以用來存儲集羣的ID。
processArgs(建立進程啓動服務端)
/** * Process server start command */ var processArgs = function (app, args) { var serverType = args.serverType || Constants.RESERVED.MASTER; var serverId = args.id || app.getMaster().id; var mode = args.mode || Constants.RESERVED.CLUSTER; var masterha = args.masterha || 'false'; var type = args.type || Constants.RESERVED.ALL; var startId = args.startId; app.set(Constants.RESERVED.MAIN, args.main, true); app.set(Constants.RESERVED.SERVER_TYPE, serverType, true); app.set(Constants.RESERVED.SERVER_ID, serverId, true); app.set(Constants.RESERVED.MODE, mode, true); app.set(Constants.RESERVED.TYPE, type, true); if (!!startId) { app.set(Constants.RESERVED.STARTID, startId, true); } if (masterha === 'true') { app.master = args; app.set(Constants.RESERVED.CURRENT_SERVER, args, true); } else if (serverType !== Constants.RESERVED.MASTER) { app.set(Constants.RESERVED.CURRENT_SERVER, args, true); } else { app.set(Constants.RESERVED.CURRENT_SERVER, app.getMaster(), true); } };
這個方法設置了app的一些屬性參數值,後兩步的日誌文件和生命週期,放到後面的章節再研究,如今app的信息已經完善,至此,appUtil.appdefaultConfiguration方法執行完成,Pomelo.createApp執行完成,並將app返回給app.js文件中的app對象,思路回到app.js中,代碼繼續向下走
var bearcat = require('bearcat'); var pomelo = require('pomelo'); /** * Init app for client. */ var app = pomelo.createApp(); var Configure = function() { app.set('name', 'treasures'); app.configure('production|development', 'gate', function() { app.set('connectorConfig', { connector: pomelo.connectors.hybridconnector }); }); app.configure('production|development', 'connector', function() { app.set('connectorConfig', { connector: pomelo.connectors.hybridconnector, heartbeat: 100, useDict: true, useProtobuf: true }); }); app.configure('production|development', 'area', function() { var areaId = app.get('curServer').areaId; if (!areaId || areaId < 0) { throw new Error('load area config failed'); } var areaService = bearcat.getBean('areaService'); var dataApiUtil = bearcat.getBean('dataApiUtil'); areaService.init(dataApiUtil.area().findById(areaId)); }); }
app賦值完成以後,聲明瞭Configure對象,這裏貌似是接收消息使用的,下面來去到application.configure。
function load(path, name) { if (name) { return require(path + name); } return require(path); } /** * connectors */ Pomelo.connectors = {}; Pomelo.connectors.__defineGetter__('sioconnector', load.bind(null, './connectors/sioconnector')); Pomelo.connectors.__defineGetter__('hybridconnector', load.bind(null, './connectors/hybridconnector')); Pomelo.connectors.__defineGetter__('udpconnector', load.bind(null, './connectors/udpconnector')); Pomelo.connectors.__defineGetter__('mqttconnector', load.bind(null, './connectors/mqttconnector'));
Pomelo.connectors.__defineGetter__('hybridconnector', load.bind(null, './connectors/hybridconnector')); 將 game-server/node_modules/pomelo/connectors/hybridconnector.js的引用賦值給app中的connectorConfig屬性設置,這個connector能夠看作是一個連接的控制器,後續的操做將圍繞着這個connector對象來開展。
至此,app的建立準備工做便完成了。
下面是重要的一步,程序開始,經過start方法啓動服務端
關於更多請關注 bearcat 的介紹
從代碼中能夠看出這個app已經啓動完成,在這個期間有還有一個在application文件中的對象Constants,constants文件是記錄程序中的一些基礎的配置。
module.exports = { KEYWORDS: { BEFORE_FILTER: '__befores__', AFTER_FILTER: '__afters__', GLOBAL_BEFORE_FILTER: '__globalBefores__', GLOBAL_AFTER_FILTER: '__globalAfters__', ROUTE: '__routes__', BEFORE_STOP_HOOK: '__beforeStopHook__', MODULE: '__modules__', SERVER_MAP: '__serverMap__', RPC_BEFORE_FILTER: '__rpcBefores__', RPC_AFTER_FILTER: '__rpcAfters__', MASTER_WATCHER: '__masterwatcher__', MONITOR_WATCHER: '__monitorwatcher__' }, FILEPATH: { MASTER: '/config/master.json', SERVER: '/config/servers.json', CRON: '/config/crons.json', LOG: '/config/log4js.json', SERVER_PROTOS: '/config/serverProtos.json', CLIENT_PROTOS: '/config/clientProtos.json', MASTER_HA: '/config/masterha.json', LIFECYCLE: '/lifecycle.js', SERVER_DIR: '/app/servers/', CONFIG_DIR: '/config' }, DIR: { HANDLER: 'handler', REMOTE: 'remote', CRON: 'cron', LOG: 'logs', SCRIPT: 'scripts', EVENT: 'events', COMPONENT: 'components' }, RESERVED: { BASE: 'base', MAIN: 'main', MASTER: 'master', SERVERS: 'servers', ENV: 'env', CPU: 'cpu', ENV_DEV: 'development', ENV_PRO: 'production', ALL: 'all', SERVER_TYPE: 'serverType', SERVER_ID: 'serverId', CURRENT_SERVER: 'curServer', MODE: 'mode', TYPE: 'type', CLUSTER: 'clusters', STAND_ALONE: 'stand-alone', START: 'start', AFTER_START: 'afterStart', CRONS: 'crons', ERROR_HANDLER: 'errorHandler', GLOBAL_ERROR_HANDLER: 'globalErrorHandler', AUTO_RESTART: 'auto-restart', RESTART_FORCE: 'restart-force', CLUSTER_COUNT: 'clusterCount', CLUSTER_PREFIX: 'cluster-server-', CLUSTER_SIGNAL: '++', RPC_ERROR_HANDLER: 'rpcErrorHandler', SERVER: 'server', CLIENT: 'client', STARTID: 'startId', STOP_SERVERS: 'stop_servers', SSH_CONFIG_PARAMS: 'ssh_config_params' }, COMMAND: { TASKSET: 'taskset', KILL: 'kill', TASKKILL: 'taskkill', SSH: 'ssh' }, PLATFORM: { WIN: 'win32', LINUX: 'linux' }, LIFECYCLE: { BEFORE_STARTUP: 'beforeStartup', BEFORE_SHUTDOWN: 'beforeShutdown', AFTER_STARTUP: 'afterStartup', AFTER_STARTALL: 'afterStartAll' }, SIGNAL: { FAIL: 0, OK: 1 }, TIME: { TIME_WAIT_STOP: 3 * 1000, TIME_WAIT_KILL: 5 * 1000, TIME_WAIT_RESTART: 5 * 1000, TIME_WAIT_COUNTDOWN: 10 * 1000, TIME_WAIT_MASTER_KILL: 2 * 60 * 1000, TIME_WAIT_MONITOR_KILL: 2 * 1000, TIME_WAIT_PING: 30 * 1000, TIME_WAIT_MAX_PING: 5 * 60 * 1000, DEFAULT_UDP_HEARTBEAT_TIME: 20 * 1000, DEFAULT_UDP_HEARTBEAT_TIMEOUT: 100 * 1000, DEFAULT_MQTT_HEARTBEAT_TIMEOUT: 90 * 1000 } };
因爲基礎太差須要好好吸取一下,本次就學到這,下章繼續~