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阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容