码迷,mamicode.com
首页 > 其他好文 > 详细

engine.io客户端分析2--socket.io的基石

时间:2014-12-18 18:31:42      阅读:181      评论:0      收藏:0      [点我收藏+]

标签:des   style   blog   http   ar   io   color   os   sp   

转载请注明: TheViper http://www.cnblogs.com/TheViper  

上一篇说到收到握手响应后的packet.type=open。接着是onHandshake()

Socket.prototype.onHandshake = function (data) {
  this.emit(‘handshake‘, data);
  this.id = data.sid;
  this.transport.query.sid = data.sid;
  this.upgrades = this.filterUpgrades(data.upgrades);
  this.pingInterval = data.pingInterval;
  this.pingTimeout = data.pingTimeout;
  this.onOpen();
  // In case open handler closes socket
  if  (‘closed‘ == this.readyState) return;
  this.setPing();

  // Prolong liveness of socket on heartbeat
  this.removeListener(‘heartbeat‘, this.onHeartbeat);
  this.on(‘heartbeat‘, this.onHeartbeat);
};
Socket.prototype.onOpen = function () {
  debug(‘socket open‘);
  this.readyState = ‘open‘;
  this.emit(‘open‘);
  this.flush();
};
Socket.prototype.flush = function () {
  if (‘closed‘ != this.readyState && this.transport.writable &&
    !this.upgrading && this.writeBuffer.length) {
    .......
  this.transport.send(this.writeBuffer); } };

writeBuffer里面没有内容,不会走里面。

Polling.prototype.onData = function(data){
  .....

  // decode payload
  parser.decodePayload(data, this.socket.binaryType, callback);

  // if an event did not trigger closing
  if (‘closed‘ != this.readyState) {
    // if we got data we‘re not polling
    this.polling = false;
    this.emit(‘pollComplete‘);

    if (‘open‘ == this.readyState) {
      this.poll();
    } else {
      debug(‘ignoring poll - transport state "%s"‘, this.readyState);
    }
  }
};

新的get请求已经在之前onData里面的poll()建立了。

如果有数据,立刻用send,以post请求的方式,将数据传给服务端。比如,聊天的文字。

post请求除了用来维持心跳,还负责将客户端的数据传给服务端,长连接的get请求已经单独发出了,不能用其传递数据了,就只有用post请求了。

然后是setPing()

Socket.prototype.setPing = function () {
  var self = this;
  clearTimeout(self.pingIntervalTimer);
  self.pingIntervalTimer = setTimeout(function () {
    debug(‘writing ping packet - expecting pong within %sms‘, self.pingTimeout);
    self.ping();
    self.onHeartbeat(self.pingTimeout);
  }, self.pingInterval);
};

 ping()用来向服务端发送心跳。

Socket.prototype.ping = function () {
  this.sendPacket(‘ping‘);
};

 至此,握手结束。

 

话说握手的时候,服务端会setPingTimeout();

Socket.prototype.setPingTimeout = function () {
  var self = this;
  clearTimeout(self.pingTimeoutTimer);
  self.pingTimeoutTimer = setTimeout(function () {
    self.onClose(‘ping timeout‘);
  }, self.server.pingTimeout);
};

服务端会看在pingTimeout时间内,客户端有没有传送post请求,证明自己还在。

而客户端,在握手成功后,会setPing(),这个上面有。

然后等啊等,重要到pingInterval时间了,客户端发送post请求,this.sendPacket(‘ping‘);

另外,如果是客户端主动发送数据的话。

Socket.prototype.write =
Socket.prototype.send = function (msg, fn) {
  this.sendPacket(‘message‘, msg, fn);
  return this;
};

可以看到也是调用了sendPacket.

Socket.prototype.sendPacket = function (type, data, fn) {
  if (‘closing‘ == this.readyState || ‘closed‘ == this.readyState) {
    return;
  }

  var packet = { type: type, data: data };
  this.emit(‘packetCreate‘, packet);
  this.writeBuffer.push(packet);
  this.callbackBuffer.push(fn);
  this.flush();
};
Socket.prototype.flush = function () {
  if (‘closed‘ != this.readyState && this.transport.writable &&
    !this.upgrading && this.writeBuffer.length) {
    debug(‘flushing %d packets in socket‘, this.writeBuffer.length);
    this.transport.send(this.writeBuffer);
    // keep track of current length of writeBuffer
    // splice writeBuffer and callbackBuffer on `drain`
    this.prevBufferLen = this.writeBuffer.length;
    this.emit(‘flush‘);
  }
};

然后flush()

Transport.prototype.send = function(packets){
  if (‘open‘ == this.readyState) {
    this.write(packets);
  } else {
    throw new Error(‘Transport not open‘);
  }
};
Polling.prototype.write = function(packets){
  var self = this;
  this.writable = false;
  var callbackfn = function() {
    self.writable = true;
    self.emit(‘drain‘);
  };

  var self = this;
  parser.encodePayload(packets, this.supportsBinary, function(data) {
    self.doWrite(data, callbackfn);
  });
};
XHR.prototype.doWrite = function(data, fn){
  var isBinary = typeof data !== ‘string‘ && data !== undefined;
  var req = this.request({ method: ‘POST‘, data: data, isBinary: isBinary });
  var self = this;
  req.on(‘success‘, fn);
  req.on(‘error‘, function(err){
    self.onError(‘xhr post error‘, err);
  });
  this.sendXhr = req;
};

注意,这里request绑定了success事件,这个后面收到响应后会用到。

服务端收到ping后,结束长连接的get请求,并通过它发回pong响应。

      xhr.onreadystatechange = function(){
        if (4 != xhr.readyState) return;
        if (200 == xhr.status || 1223 == xhr.status) {
          self.onLoad();
        } else {
          // make sure the `error` event handler that‘s user-set
          // does not throw in the same tick and gets caught here
          setTimeout(function(){
            self.onError(xhr.status);
          }, 0);
        }
      };
Request.prototype.onLoad = function(){
  var data;
  try {
    var contentType;
    try {
      contentType = this.xhr.getResponseHeader(‘Content-Type‘).split(‘;‘)[0];
    } catch (e) {}
    if (contentType === ‘application/octet-stream‘) {
      data = this.xhr.response;
    } else {
      if (!this.supportsBinary) {
        data = this.xhr.responseText;
      } else {
        data = ‘ok‘;
      }
    }
  } catch (e) {
    this.onError(e);
  }
  if (null != data) {
    this.onData(data);
  }
};

onLoad()取回发回的数据。

Request.prototype.onSuccess = function(){
  this.emit(‘success‘);
  this.cleanup();
};

Request.prototype.onData = function(data){
  this.emit(‘data‘, data);
  this.onSuccess();
};

注意,get请求上绑定data事件,用来接收数据;post请求上绑定success事件,用来确定接收服务端心跳成功。

对get请求,this.emit(‘data‘, data);这个在前面说收到握手响应的时候说过。只是最后解析出来的type是pong。

Socket.prototype.onPacket = function (packet) {
  if (‘opening‘ == this.readyState || ‘open‘ == this.readyState) {
    debug(‘socket receive: type "%s", data "%s"‘, packet.type, packet.data);

    this.emit(‘packet‘, packet);

    // Socket is live - any packet counts
    this.emit(‘heartbeat‘);

    switch (packet.type) {
      case ‘open‘:
        this.onHandshake(parsejson(packet.data));
        break;

      case ‘pong‘:
        this.setPing();
        break;

      case ‘error‘:
        var err = new Error(‘server error‘);
        err.code = packet.data;
        this.emit(‘error‘, err);
        break;

      case ‘message‘:
        this.emit(‘data‘, packet.data);
        this.emit(‘message‘, packet.data);
        break;
    }
  } else {
    debug(‘packet received with socket readyState "%s"‘, this.readyState);
  }
};

然后setPing()设置发出post请求的定时器。

对post请求的响应。注意onSuccess()里面this.emit(‘success‘);。这个在前面说发出post请求时,说到在request上绑定了success事件,这里就触发。回调函数是

  var callbackfn = function() {
    self.writable = true;
    self.emit(‘drain‘);
  };
  transport
  .on(‘drain‘, function(){
    self.onDrain();
  })
Socket.prototype.onDrain = function() {
...
  this.writeBuffer.splice(0, this.prevBufferLen);
  this.callbackBuffer.splice(0, this.prevBufferLen);

  // setting prevBufferLen = 0 is very important
  // for example, when upgrading, upgrade packet is sent over,
  // and a nonzero prevBufferLen could cause problems on `drain`
  this.prevBufferLen = 0;
  if (this.writeBuffer.length == 0) {
    this.emit(‘drain‘);
  } else {
    this.flush();
  }
};

onDrain()里面会判断writeBuffer里有没有数据。道理和服务端onPollRequest()里面的this.emit("drain")一样,为了实时性。

 最后说下,客户端新的get长连接请求是在什么时候发出的。

在解析get请求的响应时,self.onPacket()后并没有完,会调用poll()->doPoll().

Polling.prototype.onData = function(data){
  var self = this;
  debug(‘polling got data %s‘, data);
  var callback = function(packet, index, total) {
    // if its the first message we consider the transport open
    if (‘opening‘ == self.readyState) {
      self.onOpen();
    }

    // if its a close packet, we close the ongoing requests
    if (‘close‘ == packet.type) {
      self.onClose();
      return false;
    }

    // otherwise bypass onData and handle the message
    self.onPacket(packet);
  };

  // decode payload
  parser.decodePayload(data, this.socket.binaryType, callback);

  // if an event did not trigger closing
  if (‘closed‘ != this.readyState) {
    // if we got data we‘re not polling
    this.polling = false;
    this.emit(‘pollComplete‘);
    if (‘open‘ == this.readyState) {
      this.poll();
    } else {
      debug(‘ignoring poll - transport state "%s"‘, this.readyState);
    }
  }
};

 

至此,engine.io的客户端和服务端都简单的分析完了。

而里面的传输方式升级(polling->websocket),两端的jsonp传输方式具体的执行,由于本屌时间精力有限,就没有做了。

如果前面的东西理解的话,这些分析其实一点都不难。

最后,可以看到socket.io 1.x在engine.io上加了不少东西,比如,broadcast,room,namespace等,看过这几篇文章后,相信这些加上去的东西也不难分析了。

engine.io客户端分析2--socket.io的基石

标签:des   style   blog   http   ar   io   color   os   sp   

原文地址:http://www.cnblogs.com/TheViper/p/4167819.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!