pomelo源码分析(4)--connector之网络监听

作者:shihuaping0918@163.com,转载请注明作者

pomelo的connector负责接收外部连接,同时做协议的编码解码,接收的时候做解码,发送的时候做编码。如果有对消息进行加密的话,也是在这里进行处理。有unicode的话,还要转码成utf8。

connector的网络处理是基于事件的,这也符合node.js的设计。connector是一个component,根据pomelo的约定,component有start/afterStart/stop等调用,进行生命周期管理。

connector依赖connection/server/pushScheduler/session组件,它是很重量级的,内部还有各种协议的实现,典型的有protobuf,mqtt。其中protobuf大家都很熟悉了,mqtt是物联网协议,它的特点是体积小,效率高,还省电。根据大黄易提供的数据,pomelo+mqtt能够实现单机30w在线的推送。这个数字就非常惊人了,因为一般的服务器设计能够承载10k级别的在线就算是很好了。

connector的体量有这么大,一篇就分析完也是不现实的。准备分成三篇来讲,第一篇讲connector的网络相关的内容。第二篇讲协议和加解密。第三篇讲connector和其它组件的交互。

前面讲到pomelo是按约定来编程的,又是微内核+插件实现方式,所以光看代码有些东西是看不出来的,还需要结合配置来看才行。如果仅仅是看代码,可能调用关系很难理得清楚,看着看着就卡住了。

按照普通网络服务器的流程,首先是要有监听,绑定,要有host和port。然后才有连接进来,连接建立了以后,才有数据收发,编码解码。先找到监听在哪里。

还是看https://github.com/NetEase/chatofpomelo/blob/master/game-server/app.js这个项目,chatofpomelo,因为它足够简单。按第三篇的分析,它配了一个connector: pomelo.connectors.sioconnector,到conpoment/connector.js里看这个配置是怎么生效的。

connector.js是对各种不同的connector的封装,它里面有一个函数getConnector,这个函数会根据配置加载真正实现业务的connector。

var getConnector = function(app, opts) {
  var connector = opts.connector;  //配了
  if (!connector) { //有值,不进下面的函数
    return getDefaultConnector(app, opts);
  }
  //如果不是函数,也不进下面的行
  if (typeof connector !== 'function') {
    return connector;
  }
 //调用函数
  var curServer = app.getCurServer();
  return connector(curServer.clientPort, curServer.host, opts);
};

pomelo.connections.sioconnector实际上是一个函数,看export的内容

/**
 * Connector that manager low level connection and protocol bewteen server and client.
 * Develper can provide their own connector to switch the low level prototol, such as tcp or probuf.
 */
var Connector = function(port, host, opts) {
  if (!(this instanceof Connector)) {
    return new Connector(port, host, opts);
  }

  EventEmitter.call(this);
  this.port = port;
  this.host = host;
  this.opts = opts;
  this.heartbeats = opts.heartbeats || true;
  this.closeTimeout = opts.closeTimeout || 60;
  this.heartbeatTimeout = opts.heartbeatTimeout || 60;
  this.heartbeatInterval = opts.heartbeatInterval || 25;
};

util.inherits(Connector, EventEmitter);

module.exports = Connector; //这就是函数

所以可以分析得知connector.js中的connector是根据配置去connections下面加载指定的connector。没有配置不配就加载一个默认的,这个默认的就是sioconnector。

一、网络监听:
加载分析完以后,看一下启动和停止。

pro.afterStart = function(cb) {
  this.connector.start(cb);  //sioconnector.start启动
  this.connector.on('connection', hostFilter.bind(this, bindEvents));
};

pro.stop = function(force, cb) {
  if (this.connector) {
    this.connector.stop(force, cb); //sioconnector.stop停止
    this.connector = null;
    return;
  } else {
    process.nextTick(cb);
  }
};

先到sioconnector.js文件里去看一下

/**
 * Start connector to listen the specified port
 */
Connector.prototype.start = function(cb) {
  var self = this; //注意这里,this在js中是怎么变化的
  // issue https://github.com/NetEase/pomelo-cn/issues/174
  var opts = {}
  if(!!this.opts) {
    opts = this.opts;
  }
  else {
    opts = {
      transports: [
      'websocket', 'polling-xhr', 'polling-jsonp', 'polling'
      ]
    };
  }
//使用socket.io作为网络底层库
  var sio = require('socket.io')(httpServer, opts);

  var port = this.port;
  httpServer.listen(port, function () { //看到listen了
    console.log('sio Server listening at port %d', port);
  });
  sio.set('resource', '/socket.io');
  sio.set('transports', this.opts.transports);
  sio.set('heartbeat timeout', this.heartbeatTimeout);
  sio.set('heartbeat interval', this.heartbeatInterval);
//有连接进来就触发回调
  sio.on('connection', function (socket) {
    var siosocket = new SioSocket(curId++, socket);
    self.emit('connection', siosocket);  //触发事件
    siosocket.on('closing', function(reason) {
      siosocket.send({route: 'onKick', reason: reason});
    });
  });

  process.nextTick(cb);
};

从sioconnector.js中可以看到,配置不仅是零散地配,还零散地读。这是pomelo非常不好的一个地方,没有一个集中的配置管理。listen就是网络端口监听,监听成功了,外部才能和服务器建立网络连接。

二、连接建立
代码中已经看到了listen和connection事件,从代码看,sioconnection只关注连接的建立和关闭。数据的读取它不关心。连接建立的时候它手动触发了一个事件,叫connection,在这个事件里,把socket和连接id给传进去了。

下面去看一下,这个事件发出去以后被谁接收了,又是怎么处理的。先离开sioconnector.js回到connector.js。

pro.afterStart = function(cb) {
  this.connector.start(cb);
//就是它接收了connection事件
  this.connector.on('connection', hostFilter.bind(this, bindEvents));
};

代码中显示sioconnector.js发出的connection事件被connector.js所接收,而且还和一个bindEvents函数有关。

var bindEvents = function(self, socket) {
  var curServer = self.app.getCurServer();
  var maxConnections = curServer['max-connections'];
  if (self.connection && maxConnections) {
    self.connection.increaseConnectionCount();
    var statisticInfo = self.connection.getStatisticsInfo();
    if (statisticInfo.totalConnCount > maxConnections) {
      logger.warn('the server %s has reached the max connections %s', curServer.id, maxConnections);
      socket.disconnect();
      return;
    }
  }

  //create session for connection
  var session = getSession(self, socket);
  var closed = false;
  //网络断开
  socket.on('disconnect', function() {
    if (closed) {
      return;
    }
    closed = true;
    if (self.connection) {
      self.connection.decreaseConnectionCount(session.uid);
    }
  });
  //网络错误
  socket.on('error', function() {
    if (closed) {
      return;
    }
    closed = true;
    if (self.connection) {
      self.connection.decreaseConnectionCount(session.uid);
    }
  });

  //消息读取
  // new message
  socket.on('message', function(msg) {
    var dmsg = msg;
    if (self.useAsyncCoder) {
      return handleMessageAsync(self, msg, session, socket);
    }

    if (self.decode) {
      dmsg = self.decode(msg, session);
    } else if (self.connector.decode) {
      dmsg = self.connector.decode(msg, socket);
    }
    if (!dmsg) {
      // discard invalid message
      return;
    }

    // use rsa crypto
    if (self.useCrypto) {
      var verified = verifyMessage(self, session, dmsg);
      if (!verified) {
        logger.error('fail to verify the data received from client.');
        return;
      }
    }

    handleMessage(self, session, dmsg);
  }); //on message end
};

三、消息读取
从上面的代码可以看到,connector.js中才对socket的error/message/disconnect做了处理。其中消息读取就是在socket.on('message',cb)中的回调里实现的。

消息读到以后,先进行解码——如果配了解码器的话。然后进行进行加解密操作。都正常的话,就进入后续的流程handleMessage。

到此为止,coonector的网络监听,读取,断开,错误都分析完了。至于发送就没有必要去分析了。

还留有一个小尾巴,那就是端口,端口的来源是在config/servers.json里。这是pomelo配置的一种设置,它可以在servers.json里配多个server。每个server端口不一样。

{
    "development":{
        "connector":[
             {"id":"connector-server-1", "host":"127.0.0.1", "port":4050, "clientPort": 3050, "frontend": true},
             {"id":"connector-server-2", "host":"127.0.0.1", "port":4051, "clientPort": 3051, "frontend": true},
             {"id":"connector-server-3", "host":"127.0.0.1", "port":4052, "clientPort": 3052, "frontend": true}
         ],
        "chat":[
             {"id":"chat-server-1", "host":"127.0.0.1", "port":6050},
             {"id":"chat-server-2", "host":"127.0.0.1", "port":6051},
             {"id":"chat-server-3", "host":"127.0.0.1", "port":6052}
        ],
        "gate":[
           {"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3014, "frontend": true}
        ]
    },
    "production":{
           "connector":[
             {"id":"connector-server-1", "host":"127.0.0.1", "port":4050, "clientPort": 3050, "frontend": true},
             {"id":"connector-server-2", "host":"127.0.0.1", "port":4051, "clientPort": 3051, "frontend": true},
             {"id":"connector-server-3", "host":"127.0.0.1", "port":4052, "clientPort": 3052, "frontend": true}
         ],
        "chat":[
             {"id":"chat-server-1", "host":"127.0.0.1", "port":6050},
             {"id":"chat-server-2", "host":"127.0.0.1", "port":6051},
             {"id":"chat-server-3", "host":"127.0.0.1", "port":6052}
        ],
        "gate":[
           {"id": "gate-server-1", "host": "127.0.0.1", "clientPort": 3014, "frontend": true}
        ]
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,733评论 6 342
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 0 系列目录# WEB请求处理 WEB请求处理一:浏览器请求发起处理 WEB请求处理二:Nginx请求反向代理 本...
    七寸知架构阅读 13,876评论 22 190
  • 色彩在生活中无论是有彩色还是无彩色,都有自己的表情特征,每一种色当它的纯度和明度发生变化,或者处于不同的颜色搭配关...
    你好小祝阅读 899评论 0 3
  • 中国人对意境很着重,外国人对实际很着重!换句话,中国人的是曲线思维,外国是直线思维。外国人更看重效率!中国人更看重效果!
    热爱看文章阅读 719评论 0 0