pomelo源碼分析(4)--connector之網(wǎng)絡(luò)監(jiān)聽

作者:shihuaping0918@163.com,轉(zhuǎn)載請注明作者

pomelo的connector負(fù)責(zé)接收外部連接其做,同時做協(xié)議的編碼解碼盗忱,接收的時候做解碼,發(fā)送的時候做編碼抄腔。如果有對消息進(jìn)行加密的話,也是在這里進(jìn)行處理理张。有unicode的話,還要轉(zhuǎn)碼成utf8绵患。

connector的網(wǎng)絡(luò)處理是基于事件的雾叭,這也符合node.js的設(shè)計。connector是一個component落蝙,根據(jù)pomelo的約定织狐,component有start/afterStart/stop等調(diào)用,進(jìn)行生命周期管理筏勒。

connector依賴connection/server/pushScheduler/session組件移迫,它是很重量級的,內(nèi)部還有各種協(xié)議的實現(xiàn)管行,典型的有protobuf厨埋,mqtt。其中protobuf大家都很熟悉了捐顷,mqtt是物聯(lián)網(wǎng)協(xié)議荡陷,它的特點是體積小雨效,效率高,還省電废赞。根據(jù)大黃易提供的數(shù)據(jù)徽龟,pomelo+mqtt能夠?qū)崿F(xiàn)單機(jī)30w在線的推送。這個數(shù)字就非常驚人了唉地,因為一般的服務(wù)器設(shè)計能夠承載10k級別的在線就算是很好了据悔。

connector的體量有這么大,一篇就分析完也是不現(xiàn)實的耘沼。準(zhǔn)備分成三篇來講屠尊,第一篇講connector的網(wǎng)絡(luò)相關(guān)的內(nèi)容。第二篇講協(xié)議和加解密耕拷。第三篇講connector和其它組件的交互讼昆。

前面講到pomelo是按約定來編程的,又是微內(nèi)核+插件實現(xiàn)方式骚烧,所以光看代碼有些東西是看不出來的浸赫,還需要結(jié)合配置來看才行。如果僅僅是看代碼赃绊,可能調(diào)用關(guān)系很難理得清楚既峡,看著看著就卡住了。

按照普通網(wǎng)絡(luò)服務(wù)器的流程碧查,首先是要有監(jiān)聽运敢,綁定,要有host和port忠售。然后才有連接進(jìn)來传惠,連接建立了以后,才有數(shù)據(jù)收發(fā)稻扬,編碼解碼卦方。先找到監(jiān)聽在哪里。

還是看https://github.com/NetEase/chatofpomelo/blob/master/game-server/app.js這個項目泰佳,chatofpomelo盼砍,因為它足夠簡單。按第三篇的分析逝她,它配了一個connector: pomelo.connectors.sioconnector浇坐,到conpoment/connector.js里看這個配置是怎么生效的。

connector.js是對各種不同的connector的封裝黔宛,它里面有一個函數(shù)getConnector近刘,這個函數(shù)會根據(jù)配置加載真正實現(xiàn)業(yè)務(wù)的connector。

var getConnector = function(app, opts) {
  var connector = opts.connector;  //配了
  if (!connector) { //有值,不進(jìn)下面的函數(shù)
    return getDefaultConnector(app, opts);
  }
  //如果不是函數(shù)跌宛,也不進(jìn)下面的行
  if (typeof connector !== 'function') {
    return connector;
  }
 //調(diào)用函數(shù)
  var curServer = app.getCurServer();
  return connector(curServer.clientPort, curServer.host, opts);
};

pomelo.connections.sioconnector實際上是一個函數(shù)酗宋,看export的內(nèi)容

/**
 * 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; //這就是函數(shù)

所以可以分析得知connector.js中的connector是根據(jù)配置去connections下面加載指定的connector。沒有配置不配就加載一個默認(rèn)的疆拘,這個默認(rèn)的就是sioconnector蜕猫。

一、網(wǎng)絡(luò)監(jiān)聽:
加載分析完以后哎迄,看一下啟動和停止回右。

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作為網(wǎng)絡(luò)底層庫
  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);
//有連接進(jìn)來就觸發(fā)回調(diào)
  sio.on('connection', function (socket) {
    var siosocket = new SioSocket(curId++, socket);
    self.emit('connection', siosocket);  //觸發(fā)事件
    siosocket.on('closing', function(reason) {
      siosocket.send({route: 'onKick', reason: reason});
    });
  });

  process.nextTick(cb);
};

從sioconnector.js中可以看到漱挚,配置不僅是零散地配翔烁,還零散地讀。這是pomelo非常不好的一個地方旨涝,沒有一個集中的配置管理蹬屹。listen就是網(wǎng)絡(luò)端口監(jiān)聽,監(jiān)聽成功了白华,外部才能和服務(wù)器建立網(wǎng)絡(luò)連接慨默。

二、連接建立
代碼中已經(jīng)看到了listen和connection事件弧腥,從代碼看厦取,sioconnection只關(guān)注連接的建立和關(guān)閉。數(shù)據(jù)的讀取它不關(guān)心管搪。連接建立的時候它手動觸發(fā)了一個事件虾攻,叫connection,在這個事件里更鲁,把socket和連接id給傳進(jìn)去了霎箍。

下面去看一下,這個事件發(fā)出去以后被誰接收了岁经,又是怎么處理的朋沮。先離開sioconnector.js回到connector.js。

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

代碼中顯示sioconnector.js發(fā)出的connection事件被connector.js所接收缀壤,而且還和一個bindEvents函數(shù)有關(guān)。

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;
  //網(wǎng)絡(luò)斷開
  socket.on('disconnect', function() {
    if (closed) {
      return;
    }
    closed = true;
    if (self.connection) {
      self.connection.decreaseConnectionCount(session.uid);
    }
  });
  //網(wǎng)絡(luò)錯誤
  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)中的回調(diào)里實現(xiàn)的蒂胞。

消息讀到以后图呢,先進(jìn)行解碼——如果配了解碼器的話。然后進(jìn)行進(jìn)行加解密操作。都正常的話蛤织,就進(jìn)入后續(xù)的流程handleMessage赴叹。

到此為止,coonector的網(wǎng)絡(luò)監(jiān)聽指蚜,讀取乞巧,斷開,錯誤都分析完了摊鸡。至于發(fā)送就沒有必要去分析了绽媒。

還留有一個小尾巴,那就是端口免猾,端口的來源是在config/servers.json里是辕。這是pomelo配置的一種設(shè)置,它可以在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}
        ]
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市锨苏,隨后出現(xiàn)的幾起案子疙教,更是在濱河造成了極大的恐慌,老刑警劉巖蚓炬,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件松逊,死亡現(xiàn)場離奇詭異,居然都是意外死亡肯夏,警方通過查閱死者的電腦和手機(jī)经宏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來驯击,“玉大人烁兰,你說我怎么就攤上這事』捕迹” “怎么了沪斟?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長暇矫。 經(jīng)常有香客問我主之,道長,這世上最難降的妖魔是什么李根? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任槽奕,我火速辦了婚禮,結(jié)果婚禮上房轿,老公的妹妹穿的比我還像新娘粤攒。我一直安慰自己所森,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布夯接。 她就那樣靜靜地躺著焕济,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盔几。 梳的紋絲不亂的頭發(fā)上晴弃,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機(jī)與錄音问欠,去河邊找鬼肝匆。 笑死,一個胖子當(dāng)著我的面吹牛顺献,可吹牛的內(nèi)容都是我干的旗国。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼注整,長吁一口氣:“原來是場噩夢啊……” “哼能曾!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起肿轨,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤寿冕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后椒袍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驼唱,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年驹暑,在試婚紗的時候發(fā)現(xiàn)自己被綠了玫恳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡优俘,死狀恐怖京办,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情帆焕,我是刑警寧澤惭婿,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站叶雹,受9級特大地震影響财饥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜折晦,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一佑力、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧筋遭,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至响驴,卻和暖如春透且,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背豁鲤。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工秽誊, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人琳骡。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓锅论,卻偏偏與公主長得像,于是被迫代替她去往敵國和親楣号。 傳聞我的和親對象是個殘疾皇子最易,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容

  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,778評論 6 342
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)炫狱,斷路器藻懒,智...
    卡卡羅2017閱讀 134,637評論 18 139
  • 0 系列目錄# WEB請求處理 WEB請求處理一:瀏覽器請求發(fā)起處理 WEB請求處理二:Nginx請求反向代理 本...
    七寸知架構(gòu)閱讀 13,921評論 22 190
  • 色彩在生活中無論是有彩色還是無彩色,都有自己的表情特征视译,每一種色當(dāng)它的純度和明度發(fā)生變化嬉荆,或者處于不同的顏色搭配關(guān)...
    你好小祝閱讀 904評論 0 3
  • 中國人對意境很著重鄙早,外國人對實際很著重!換句話第美,中國人的是曲線思維蝶锋,外國是直線思維。外國人更看重效率什往!中國人更看重效果扳缕!
    熱愛看文章閱讀 721評論 0 0