天天看點

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與sessionService分析

pomelo為每一個連接配接都配置設定了session,而且還要處理session與uid之間的關系。。。

還在session中封裝了一些發送資料的方法。。。。還算比較好用吧。。。

轉自https://www.xuebuyuan.com/2041523.html