在看pomelo的session之前,我們先來看看pomelo的元件加載過程:
if(app.isFrontend()) { //前端伺服器 才需要載入的元件
app.load(pomelo.connection, app.get('connectionConfig')); //connection元件用于維護連接配接,比如說連接配接的狀态
app.load(pomelo.connector, app.get('connectorConfig')); //用于從用戶端接收socket,并為其配置設定session等
app.load(pomelo.session, app.get('sessionConfig')); //用于維護管理session,隻有前端伺服器才有session
app.load(pomelo.protobuf, app.get('protobufConfig'));
app.load(pomelo.scheduler, app.get('schedulerConfig'));
}
app.load(pomelo.localSession, app.get('localSessionConfig'));
app.load(pomelo.channel, app.get('channelConfig')); //channel的維護
app.load(pomelo.server, app.get('serverConfig')); //目前server的具體一些東西,例如一些使用者定義的handler等
if(app.get('globalChannelConfig')) {
app.load(pomelo.globalChannel, app.get('globalChannelConfig'));
}
由上面的代碼可以看出,隻有frontend類型的伺服器才會有session,其餘的則隻有localsession,這個也很好了解,因為也隻有frontend類型的伺服器才會接受使用者的連接配接,要搞清楚session這部分,那麼還需要從socket的接收開始說起了。。
在前面的文章中可以知道,pomelo中,frontend類型的伺服器會加載connector元件,用于接收使用者的連接配接,而且一般情況下都是采用的websocket的形式。。
那麼我們就來看看這裡websocket接收到socket執行的操作吧:
this.wsocket.sockets.on('connection', function (socket) { //當有新的連接配接建立的時候
var siosocket = new SioSocket(curId++, socket);
self.emit('connection', siosocket); //本身出發connector事件,用于通知外面的connector
siosocket.on('closing', function(reason) {
if(reason === 'kick') {
siosocket.send({route: 'onKick'});
}
});
});
這裡pomelo對接收到的socket又進行了一層的封裝,自己定義了一個siosocket,其實也比較的簡單,來看看它的構造過程:
var Socket = function(id, socket) {
EventEmitter.call(this);
this.id = id; //id号
this.socket = socket; //儲存真正的socket
this.remoteAddress = {
ip: socket.handshake.address.address,
port: socket.handshake.address.port
};
var self = this;
socket.on('disconnect', this.emit.bind(this, 'disconnect'));
socket.on('error', this.emit.bind(this, 'error'));
socket.on('message', function(msg) { //當接受到資料之後
self.emit('message', msg);
});
this.state = ST_INITED;
// TODO: any other events?
};
說白了就是兩個比較重要的屬性:配置設定的id和真正的socket,然後設定了事件處理的函數。。。
在建立了自己定義的siosocket之後,會激發connection事件,來看看這個是怎麼處理的吧:
this.connector.on('connection', function(socket) { //為connector綁定事件處理函數
bindEvents(self, socket);
}); //on connection end
也就是為目前的siosocket綁定一些事件的處理函數,而且在裡面會具體的為目前的連接配接配置設定session。。。
那麼我們來看看這個函數:
var bindEvents = function(self, socket) {
if(self.connection) {
self.connection.increaseConnectionCount(); //增加目前connection的連接配接數目
}
//create session for connection
var session = getSession(self, socket); //為目前的socket配置設定session
var closed = false;
socket.on('disconnect', function() { //表示連接配接已經斷開
if(closed) {
return;
}
closed = true; //表示目前的connection已經關閉了
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.decode) {
dmsg = self.decode(msg);
} else if(self.connector.decode) {
dmsg = self.connector.decode(msg);
}
if(!dmsg) {
// discard invalid message
return;
}
handleMessage(self, session, dmsg); //這裡是調用server具體的handler來處理收到的這些資料
}); //on message end
};
這裡看到了吧,首先還為目前的connection配置設定了一個session,然後再進行其餘的處理。。具體都在幹什麼,其實前面的文章應該也比較的清楚了。。那麼就來看看session是怎麼弄出來的吧:
var getSession = function(self, socket) {
var app = self.app, sid = socket.id;
var session = self.session.get(sid); //直接從key-value裡面取,如果沒有的話,那麼再建立
if(session) {
return session;
}
//用于建立session的參數有目前的socket的id,還有server的id,還有socket
session = self.session.create(sid, app.getServerId(), socket);
// bind events for session
socket.on('disconnect', session.closed.bind(session));
socket.on('error', session.closed.bind(session));
session.on('closed', onSessionClose.bind(null, app));
session.on('bind', function(uid) { //當有user綁定到目前的session上面來
// update connection statistics if necessary
if(self.connection) {
self.connection.addLoginedUser(uid, {
loginTime: Date.now(),
uid: uid,
address: socket.remoteAddress.ip + ':' + socket.remoteAddress.port
});
}
});
return session;
};
這裡其實調用的是sessionservice的create來建立的session,而且傳進去的參數也還算是比較多的。。有目前socket的id,server的id還有socket,那麼我們來看看這個create函數做了什麼事情吧:
//建構新的session,這裡的sid就是socket的id,frontendId是伺服器的id,socket就是目前的siosocket
SessionService.prototype.create = function(sid, frontendId, socket) {
var session = new Session(sid, frontendId, socket, this); //建立session
this.sessions[session.id] = session; //用socket的id來索引這個session
return session;
};
直接調用session的構造函數來建立session,而且還用session的id來索引這個session,其實這裡這個id也就是socket的id。。我們來看看這個session的構造函數吧:
//session的構造函數
var Session = function(sid, frontendId, socket, service) {
EventEmitter.call(this);
this.id = sid; // session的id,其實就是socket的id
this.frontendId = frontendId; // 伺服器id
this.uid = null; // r
this.settings = {}; //用于儲存set的屬性
// private
this.__socket__ = socket;
this.__sessionService__ = service; //一般情況下會這是為null
this.__state__ = ST_INITED;
};
util.inherits(Session, EventEmitter);
好了,那麼到現在為止,整個session的建立過程就算弄完了。。
接下來我們在來看看整個sessionService所提供的一些服務吧,首先來看sessionService的構造:
var SessionService = function(opts) {
opts = opts || {};
this.sessions = {}; // sid -> session
this.uidMap = {}; // uid -> sessions //用于将uid與session綁定起來
};
這裡sessions用于儲存目前伺服器所有的session,其中key是session的id,value則是對應的session
uidMap則是用于組織一個uid對應的所有的session,因為一個uid可能擁有多個session,這個也是需要組織起來的。。
然後來看幾個比較重要的方法,首先是bind方法,用于将一個session與一個uid綁定起來:
//sid所屬的session與uid放頂起來
SessionService.prototype.bind = function(sid, uid, cb) {
var session = this.sessions[sid]; //擷取相應的session
if(!session) {
process.nextTick(function() {
cb(new Error('session not exist, sid: ' + sid));
});
return;
}
if(session.uid) {
if(session.uid === uid) {
// already binded with the same uid
cb();
return;
}
// already binded with other uid
process.nextTick(function() {
cb(new Error('session has already bind with ' + session.uid));
});
return;
}
var sessions = this.uidMap[uid]; //目前uid所有的session
if(!sessions) {
sessions = this.uidMap[uid] = [];
}
for(var i=0, l=sessions.length; i<l; i++) { //判斷目前uid所有的session是否有目前這個session
// session has binded with the uid
if(sessions[i].id === session.id) {
process.nextTick(cb);
return;
}
}
//表示這個uid有了一個新的session,那麼需要将它加入到uid的session隊列當中
sessions.push(session);
//将這個session與uid幫頂起來
session.bind(uid);
if(cb) {
process.nextTick(cb);
}
};
代碼很簡單,一看就能明白,就是多了個uidMaps的處理。。。
接下來是比較重要的發送資料的方法:
//通過這個session來發送資料
SessionService.prototype.sendMessage = function(sid, msg) {
var session = this.sessions[sid];
if(!session) {
logger.debug('fail to send message for session not exits');
return false;
}
return send(this, session, msg);
};
其實還是很簡答的,通過sid來找到相應的session,然後通過這個session來将msg發送出去。。其實最終還是調用的相應的socket來發送的資料。。。
然後就還有一個:
//為目前這個uid的所有session廣播資料
SessionService.prototype.sendMessageByUid = function(uid, msg) {
var sessions = this.uidMap[uid];
if(!sessions) {
logger.debug('fail to send message by uid for session not exist. uid: %j',
uid);
return false;
}
for(var i=0, l=sessions.length; i<l; i++) {
send(this, sessions[i], msg);
}
};
就是目前uid擁有的所有的session都要發送這個資料。。。。。
好了,到這裡分析的也差不多了。。用一張圖來總結一下好了。。:

pomelo為每一個連接配接都配置設定了session,而且還要處理session與uid之間的關系。。。
還在session中封裝了一些發送資料的方法。。。。還算比較好用吧。。。
轉自https://www.xuebuyuan.com/2041523.html