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中封装了一些发送数据的方法。。。。还算比较好用吧。。。