作者: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}
]
}
}