pomelo之session与sessionService分析


在看pomelo的session之前,我们先来看看pomelo的组件加载过程:
[javascript] view plain copy print ?
  1. if(app.isFrontend()) {  //前端服务器 才需要载入的组件  
  2.    app.load(pomelo.connection, app.get('connectionConfig'));  //connection组件用于维护连接,比如说连接的状态  
  3.    app.load(pomelo.connector, app.get('connectorConfig'));    //用于从客户端接收socket,并为其分配session等  
  4.    app.load(pomelo.session, app.get('sessionConfig'));    //用于维护管理session,只有前端服务器才有session  
  5.    app.load(pomelo.protobuf, app.get('protobufConfig'));  
  6.    app.load(pomelo.scheduler, app.get('schedulerConfig'));  
  7.  }  
  8.  app.load(pomelo.localSession, app.get('localSessionConfig'));  
  9.  app.load(pomelo.channel, app.get('channelConfig'));   //channel的维护  
  10.  app.load(pomelo.server, app.get('serverConfig'));    //当前server的具体一些东西,例如一些用户定义的handler等  
  11.  if(app.get('globalChannelConfig')) {  
  12.    app.load(pomelo.globalChannel, app.get('globalChannelConfig'));  
  13.  }  

由上面的代码可以看出,只有frontend类型的服务器才会有session,其余的则只有localsession,这个也很好理解,因为也只有frontend类型的服务器才会接受用户的连接,要搞清楚session这部分,那么还需要从socket的接收开始说起了。。

在前面的文章中可以知道,pomelo中,frontend类型的服务器会加载connector组件,用于接收用户的连接,而且一般情况下都是采用的websocket的形式。。

那么我们就来看看这里websocket接收到socket执行的操作吧:

[javascript] view plain copy print ?
  1. this.wsocket.sockets.on('connection'function (socket) {  //当有新的连接建立的时候  
  2.     var siosocket = new SioSocket(curId++, socket);  
  3.     self.emit('connection', siosocket);  //本身出发connector事件,用于通知外面的connector  
  4.     siosocket.on('closing'function(reason) {  
  5.       if(reason === 'kick') {  
  6.         siosocket.send({route: 'onKick'});  
  7.       }  
  8.     });  
  9.   });  
这里pomelo对接收到的socket又进行了一层的封装,自己定义了一个siosocket,其实也比较的简单,来看看它的构造过程:
[javascript] view plain copy print ?
  1. var Socket = function(id, socket) {  
  2.   EventEmitter.call(this);  
  3.   this.id = id;  //id号  
  4.   this.socket = socket;  //保存真正的socket  
  5.   this.remoteAddress = {  
  6.     ip: socket.handshake.address.address,  
  7.     port: socket.handshake.address.port  
  8.   };  
  9.   
  10.   var self = this;  
  11.   
  12.   socket.on('disconnect'this.emit.bind(this'disconnect'));  
  13.   
  14.   socket.on('error'this.emit.bind(this'error'));  
  15.   
  16.   socket.on('message'function(msg) {  //当接受到数据之后  
  17.     self.emit('message', msg);  
  18.   });  
  19.   
  20.   this.state = ST_INITED;  
  21.   
  22.   // TODO: any other events?  
  23. };  
说白了就是两个比较重要的属性:分配的id和真正的socket,然后设置了事件处理的函数。。。

在创建了自己定义的siosocket之后,会激发connection事件,来看看这个是怎么处理的吧:

[javascript] view plain copy print ?
  1. this.connector.on('connection'function(socket) {  //为connector绑定事件处理函数  
  2.   bindEvents(self, socket);  
  3. }); //on connection end  
也就是为当前的siosocket绑定一些事件的处理函数,而且在里面会具体的为当前的连接分配session。。。

那么我们来看看这个函数:

[javascript] view plain copy print ?
  1. var bindEvents = function(self, socket) {  
  2.   if(self.connection) {  
  3.     self.connection.increaseConnectionCount();  //增加当前connection的连接数目  
  4.   }  
  5.   
  6.   //create session for connection  
  7.   var session = getSession(self, socket);  //为当前的socket分配session  
  8.   var closed = false;  
  9.   
  10.   socket.on('disconnect'function() {  //表示连接已经断开  
  11.     if(closed) {  
  12.       return;  
  13.     }  
  14.     closed = true;  //表示当前的connection已经关闭了  
  15.     if(self.connection) {  
  16.       self.connection.decreaseConnectionCount(session.uid);  
  17.     }  
  18.   });  
  19.   
  20.   socket.on('error'function() {  //连接发生了错误  
  21.     if(closed) {  
  22.       return;  
  23.     }  
  24.     closed = true;  
  25.     if(self.connection) {  
  26.       self.connection.decreaseConnectionCount(session.uid);  
  27.     }  
  28.   });  
  29.   
  30.   // new message    
  31.   socket.on('message'function(msg) {//表示接收到数据  
  32.     var dmsg = msg;  
  33.     if(self.decode) {  
  34.       dmsg = self.decode(msg);  
  35.     } else if(self.connector.decode) {  
  36.       dmsg = self.connector.decode(msg);  
  37.     }  
  38.     if(!dmsg) {  
  39.       // discard invalid message  
  40.       return;  
  41.     }  
  42.   
  43.     handleMessage(self, session, dmsg);   //这里是调用server具体的handler来处理收到的这些数据  
  44.   }); //on message end  
  45. };  
这里看到了吧,首先还为当前的connection分配了一个session,然后再进行其余的处理。。具体都在干什么,其实前面的文章应该也比较的清楚了。。那么就来看看session是怎么弄出来的吧:
[javascript] view plain copy print ?
  1. var getSession = function(self, socket) {  
  2.   var app = self.app, sid = socket.id;  
  3.   var session = self.session.get(sid);  //直接从key-value里面取,如果没有的话,那么再创建  
  4.   if(session) {  
  5.     return session;  
  6.   }  
  7. //用于创建session的参数有当前的socket的id,还有server的id,还有socket  
  8.   session = self.session.create(sid, app.getServerId(), socket);  
  9.   
  10.   // bind events for session  
  11.   socket.on('disconnect', session.closed.bind(session));  
  12.   socket.on('error', session.closed.bind(session));  
  13.   session.on('closed', onSessionClose.bind(null, app));  
  14.   session.on('bind'function(uid) {  //当有user绑定到当前的session上面来  
  15.     // update connection statistics if necessary  
  16.     if(self.connection) {  
  17.       self.connection.addLoginedUser(uid, {  
  18.         loginTime: Date.now(),  
  19.         uid: uid,  
  20.         address: socket.remoteAddress.ip + ':' + socket.remoteAddress.port  
  21.       });  
  22.     }  
  23.   });  
  24.   
  25.   return session;  
  26. };  
这里其实调用的是sessionservice的create来创建的session,而且传进去的参数也还算是比较多的。。有当前socket的id,server的id还有socket,那么我们来看看这个create函数做了什么事情吧:
[javascript] view plain copy print ?
  1. //构建新的session,这里的sid就是socket的id,frontendId是服务器的id,socket就是当前的siosocket  
  2. essionService.prototype.create = function(sid, frontendId, socket) {  
  3.  var session = new Session(sid, frontendId, socket, this);  //创建session  
  4.  this.sessions[session.id] = session;   //用socket的id来索引这个session  
  5.   
  6.  return session;  
  7. ;  
直接调用session的构造函数来创建session,而且还用session的id来索引这个session,其实这里这个id也就是socket的id。。我们来看看这个session的构造函数吧:
[javascript] view plain copy print ?
  1.  //session的构造函数  
  2. var Session = function(sid, frontendId, socket, service) {  
  3.   EventEmitter.call(this);  
  4.   this.id = sid;          // session的id,其实就是socket的id  
  5.   this.frontendId = frontendId; // 服务器id  
  6.   this.uid = null;        // r  
  7.   this.settings = {};  //用于保存set的属性  
  8.   
  9.   // private  
  10.   this.__socket__ = socket;  
  11.   this.__sessionService__ = service;   //一般情况下会这是为null  
  12.   this.__state__ = ST_INITED;  
  13. };  
  14.   
  15. util.inherits(Session, EventEmitter);  
好了,那么到现在为止,整个session的创建过程就算弄完了。。

接下来我们在来看看整个sessionService所提供的一些服务吧,首先来看sessionService的构造:

[javascript] view plain copy print ?
  1. var SessionService = function(opts) {  
  2.   opts = opts || {};  
  3.   this.sessions = {};     // sid -> session  
  4.   this.uidMap = {};       // uid -> sessions  //用于将uid与session绑定起来  
  5. };  
这里sessions用于保存当前服务器所有的session,其中key是session的id,value则是对应的session

uidMap则是用于组织一个uid对应的所有的session,因为一个uid可能拥有多个session,这个也是需要组织起来的。。

然后来看几个比较重要的方法,首先是bind方法,用于将一个session与一个uid绑定起来:

[javascript] view plain copy print ?
  1.  //sid所属的session与uid放顶起来  
  2. SessionService.prototype.bind = function(sid, uid, cb) {  
  3.   var session = this.sessions[sid];  //获取相应的session  
  4.   
  5.   if(!session) {  
  6.     process.nextTick(function() {  
  7.       cb(new Error('session not exist, sid: ' + sid));  
  8.     });  
  9.     return;  
  10.   }  
  11.   
  12.   if(session.uid) {  
  13.     if(session.uid === uid) {  
  14.       // already binded with the same uid  
  15.       cb();  
  16.       return;  
  17.     }  
  18.   
  19.     // already binded with other uid  
  20.     process.nextTick(function() {  
  21.       cb(new Error('session has already bind with ' + session.uid));  
  22.     });  
  23.     return;  
  24.   }  
  25.   
  26.   var sessions = this.uidMap[uid];  //当前uid所有的session  
  27.   if(!sessions) {  
  28.     sessions = this.uidMap[uid] = [];  
  29.   }  
  30.   
  31.   for(var i=0, l=sessions.length; i<l; i++) { //判断当前uid所有的session是否有当前这个session  
  32.     // session has binded with the uid  
  33.     if(sessions[i].id === session.id) {  
  34.       process.nextTick(cb);  
  35.       return;  
  36.     }  
  37.   }  
  38.   //表示这个uid有了一个新的session,那么需要将它加入到uid的session队列当中  
  39.   sessions.push(session);  
  40. //将这个session与uid帮顶起来  
  41.   session.bind(uid);  
  42.   
  43.   if(cb) {  
  44.     process.nextTick(cb);  
  45.   }  
  46. };  
代码很简单,一看就能明白,就是多了个uidMaps的处理。。。

接下来是比较重要的发送数据的方法:

[javascript] view plain copy print ?
  1.  //通过这个session来发送数据  
  2. SessionService.prototype.sendMessage = function(sid, msg) {  
  3.   var session = this.sessions[sid];  
  4.   
  5.   if(!session) {  
  6.     logger.debug('fail to send message for session not exits');  
  7.     return false;  
  8.   }  
  9.   
  10.   return send(this, session, msg);  
  11. };  
其实还是很简答的,通过sid来找到相应的session,然后通过这个session来将msg发送出去。。其实最终还是调用的相应的socket来发送的数据。。。

然后就还有一个:

[javascript] view plain copy print ?
  1. //为当前这个uid的所有session广播数据  
  2. essionService.prototype.sendMessageByUid = function(uid, msg) {  
  3.  var sessions = this.uidMap[uid];  
  4.   
  5.  if(!sessions) {  
  6.    logger.debug('fail to send message by uid for session not exist. uid: %j',  
  7.        uid);  
  8.    return false;  
  9.  }  
  10.   
  11.  for(var i=0, l=sessions.length; i<l; i++) {  
  12.    send(this, sessions[i], msg);  
  13.  }  
  14. ;  

就是当前uid拥有的所有的session都要发送这个数据。。。。。

pomelo为每一个连接都分配了session,而且还要处理session与uid之间的关系。。。

还在session中封装了一些发送数据的方法

你可能感兴趣的:(JavaScript,session,pomelo)