美文网首页
pomelo源码分析(3)--配置设置和读取及app.load

pomelo源码分析(3)--配置设置和读取及app.load

作者: 天一阁图书管理员 | 来源:发表于2017-08-26 12:33 被阅读666次

    作者: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。这个过程我是觉得已经讲得很详细了,各位观众能不能理解就不好说了。

    相关文章

      网友评论

          本文标题:pomelo源码分析(3)--配置设置和读取及app.load

          本文链接:https://www.haomeiwen.com/subject/amaudxtx.html