[egret+pomelo]實時遊戲雜記(1)

[egret+pomelo]學習筆記(1)html

[egret+pomelo]學習筆記(2)node

[egret+pomelo]學習筆記(3)linux

資料

egretgit

pomelogithub

pomelo撿寶項目web

準備工做

1.下載並搭建pomelo項目json

2.下載pomelo撿寶項目(github上下載的,最好是看一遍git上的教程,再進行搭建會比較順利)api

3.下載的撿寶項目[Treasures] 中有簡略的項目教程,能夠幫助咱們快速搭建和熟悉撿寶項目。服務器

開始建立Egret項目:

 因我的比較熟悉egret引擎,在論壇中找到 egret  pomelo的第三方庫app

1.客戶端代碼:

使用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 }
View Code

在文件夾下新建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 }
View Code

在項目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 });
    }
}
View Code

 完整源碼下載

 

2.服務端代碼

以上步驟都是準備工做,各語言間的連接方式和代碼都不相同,若是沒有使用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
 }
};
View Code

 

因爲基礎太差須要好好吸取一下,本次就學到這,下章繼續~

相關文章
相關標籤/搜索