pomelo之session与sessionService分析

在看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

发布了39 篇原创文章 · 获赞 63 · 访问量 26万+

猜你喜欢

转载自blog.csdn.net/qq_31967569/article/details/104080596
今日推荐