pomelo源碼分析(3)--配置設置和讀取及app.load

作者:shihuaping0918@163.com,轉載請注明作者

https://github.com/NetEase/chatofpomelo/tree/master/game-server服務器為例子來說明配置讀取是怎么完成的,chatofpomelo是pomelo做為聊天服務器的例子。它的入口點是app.js。代碼內容如下:

var pomelo = require('pomelo');
var routeUtil = require('./app/util/routeUtil');
/**
 * Init app for client.
 */
var app = pomelo.createApp();
//這一行也關注一下
app.set('name', 'chatofpomelo'); 


// app configure
app.configure('production|development', function() {
    // route configures
    app.route('chat', routeUtil.chat);
//這一段代碼是我們要關心的
    app.set('connectorConfig', {
        connector: pomelo.connectors.sioconnector,
        // 'websocket', 'polling-xhr', 'polling-jsonp', 'polling'
        transports: ['websocket', 'polling'],
        heartbeats: true,
        closeTimeout: 60 * 1000,
        heartbeatTimeout: 60 * 1000,
        heartbeatInterval: 25 * 1000
    });
    // filter configures
    app.filter(pomelo.timeout());
});

// start app
app.start();

//全局吃掉未處理異常
process.on('uncaughtException', function(err) {
    console.error(' Caught exception: ' + err.stack);
});

從代碼中看,都是通過app.set添加配置——這是pomelo比較不同的地方,常見的配置都是分成多個文件,然后通過include方式集中到一個文件。或者就只有一個文件。但是pomelo的配置可以零散的這配一點,那配一點。app.set這個函數的定義在application.js里面,內容如下:

/**
 * 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;
};

這個函數的注釋寫得是非常 的詳細了,相對我前面剛分析的那些c代碼來說,那些代碼是基本沒注釋。從代碼中可以看出來,實際上是把配置加到了settings里去了。settings的初始化是setttings={},也就是初始是個空對象。

到這里配置的設置就分析完了,pomelo就是這么簡單直接。配置的key是connectorConfig,value是一個js對象。下面看一下它是在哪里讀取的。它一定是在Application.start里面讀取的。


/**
 * Start application. It would load the default components and start all the loaded components.
 *
 * @param  {Function} cb callback function
 * @memberOf Application
 */
 Application.start = function(cb) {
  this.startTime = Date.now();
  if(this.state > STATE_INITED) {
    utils.invokeCallback(cb, new Error('application has already start.'));
    return;
  }
  
  var self = this;
  appUtil.startByType(self, function() { // 先調startByType
    appUtil.loadDefaultComponents(self); //然后startByType回調從這開始
    var startUp = function() {
      appUtil.optComponents(self.loaded, Constants.RESERVED.START, function(err) {
        self.state = STATE_START;
        if(err) {
          utils.invokeCallback(cb, err);
        } else {
          logger.info('%j enter after start...', self.getServerId());
          self.afterStart(cb);
        }
      });
    };
    var beforeFun = self.lifecycleCbs[Constants.LIFECYCLE.BEFORE_STARTUP];
    if(!!beforeFun) {
      beforeFun.call(null, self, startUp);
    } else {
      startUp();
    }
  });
};

appUtil.startByType這一篇不涉及,就直接看回調了,回調第一行就是appUtil.loadDefaultComponents,這個函數會去讀設置。但是這個函數有個非常不好的行為,就是字符常量基本都是硬編碼,也就是大家常說的,在代碼里寫死。如果不來看代碼,誰也不知道這個配置就一定是要叫這個名字。


/**
 * Load default components for application.
 */
module.exports.loadDefaultComponents = function(app) {
  var pomelo = require('../pomelo');
  // load system default components
  if (app.serverType === Constants.RESERVED.MASTER) {
    app.load(pomelo.master, app.get('masterConfig'));
  } else {
    app.load(pomelo.proxy, app.get('proxyConfig'));
    if (app.getCurServer().port) {
      app.load(pomelo.remote, app.get('remoteConfig'));
    }
    if (app.isFrontend()) {
      app.load(pomelo.connection, app.get('connectionConfig')); 
//就是它,在這里被加載了
      app.load(pomelo.connector, app.get('connectorConfig'));
      app.load(pomelo.session, app.get('sessionConfig'));
      // compatible for schedulerConfig
      if(app.get('schedulerConfig')) {
        app.load(pomelo.pushScheduler, app.get('schedulerConfig'));
      } else {
        app.load(pomelo.pushScheduler, app.get('pushSchedulerConfig'));
      }
    }
    app.load(pomelo.backendSession, app.get('backendSessionConfig'));
    app.load(pomelo.channel, app.get('channelConfig'));
    app.load(pomelo.server, app.get('serverConfig'));
  }
  app.load(pomelo.monitor, app.get('monitorConfig'));
};

分析到這里還沒完成,因為只分析到了配置被讀出來,讀出來以后的行為還沒有分析到。也就是說這個配置到底用來干什么?這就要看app.load了。app.load第一個參數叫pomelo.connector,這個東西實際上是用__defineGetter__來做了一次函數的封裝,它實際上是一個函數。__defineGetter__不是標準里面的。

/**
 * Load component
 *
 * @param  {String} name    (optional) name of the component
 * @param  {Object} component component instance or factory function of the component
 * @param  {[type]} opts    (optional) construct parameters for the factory function
 * @return {Object}     app instance for chain invoke
 * @memberOf Application
 */
Application.load = function(name, component, opts) {
  if(typeof name !== 'string') { //name是函數
    opts = component;  //參數移位
    component = name; //參數移位
    name = null;
    if(typeof component.name === 'string') {
      name = component.name;
    }
  }

  if(typeof component === 'function') { //移位后component是函數
    component = component(this, opts);
  }

  if(!name && typeof component.name === 'string') {
    name = component.name;
  }

  if(name && this.components[name]) {
    // ignore duplicat component
    logger.warn('ignore duplicate component: %j', name);
    return;
  }

  this.loaded.push(component);
  if(name) {
    // components with a name would get by name throught app.components later.
    this.components[name] = component;
  }

  return this;
};

所以在load這個函數里,name傳進來實際是個函數,對這個函數做調用,把opts作為參數傳進去。這樣就實現了模塊的加載。

以pomelo.connector為例解釋一下模塊加載的過程,上面講到了pomelo.connector實際上是函數,這是怎么實現的呢?首先到pomelo.js里去搜索,肯定是找不到.connector的。因為它是通過下面這幾行代碼添加進去的。在components目錄下有一個文件叫connector.js

/**
 * Auto-load bundled components with getters.
 */
fs.readdirSync(__dirname + '/components').forEach(function (filename) {
  if (!/\.js$/.test(filename)) {
    return;
  }
//對于connector.js,返回connector
  var name = path.basename(filename, '.js'); 
//這個bind下面再講
  var _load = load.bind(null, './components/', name);
  
  Pomelo.components.__defineGetter__(name, _load);
//看這里,__defineGetter__設置了,當訪問pomelo.getter的時候,
//調_load函數
  Pomelo.__defineGetter__(name, _load);
});

從代碼中可以看出來,當pomelo.connector被訪問時,會被調用一個_load函數。

下面再來看看這個load函數做了什么?load函數實際上是執行了require,加載模塊。

function load(path, name) {
  if (name) {
    return require(path + name);
  }
  return require(path);
}

但是_load呢,是load.bind的結果。實際是把this指針設為null了。
bind的說明:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

這里的require會返回一個函數,require('connector')會返回函數。看代碼吧,conpoment/connector.js里有一句話。

module.exports = function(app, opts) {
  return new Component(app, opts);
};

這個函數才是app.load真正調用的函數,也就是pomelo.connector返回的值,也就是require返回的值。

所以app.load(pomelo.connector, app.get('connectorConfig'));這行代碼實際上是加載component/connector模塊,然后執行模塊exports的函數,將配置傳進去,從而創建component。這個過程我是覺得已經講得很詳細了,各位觀眾能不能理解就不好說了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容