首页 > 代码库 > engine.io客户端分析1--socket.io的基石

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

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

    var socket = eio(‘http://localhost:8000‘);    socket.on(‘open‘, function(){        socket.on(‘message‘, function(data){            console.log(data);        });        socket.on(‘close‘, function(){});    });

源码很简单。

socket.js

function Socket(uri, opts){  if (!(this instanceof Socket)) return new Socket(uri, opts);....  this.open();}

先是各种传递参数,初始化。然后open().

Socket.prototype.open = function () {    var  transport = this.transports[0];  this.readyState = ‘opening‘;  // Retry with the next transport if the transport is disabled (jsonp: false)  var transport;  try {    transport = this.createTransport(transport);  } catch (e) {    this.transports.shift();    this.open();    return;  }  transport.open();  this.setTransport(transport);};

设置readyState,这个是表示全局状态的变量,值有opening,open,close.然后createTransport()

Socket.prototype.createTransport = function (name) {  debug(‘creating transport "%s"‘, name);  var query = clone(this.query);  // append engine.io protocol identifier  query.EIO = parser.protocol;  // transport name  query.transport = name;  // session id if we already have one  if (this.id) query.sid = this.id;  var transport = new transports[name]({    agent: this.agent, 。。。  });  return transport;};

这里我们不考虑websocket.

new transprots[name]在/transports/index.js.

function polling(opts){ ....  xhr = new XMLHttpRequest(opts);  if (‘open‘ in xhr && !opts.forceJSONP) {    return new XHR(opts);  } else {    if (!jsonp) throw new Error(‘JSONP disabled‘);    return new JSONP(opts);  }}

如果要强制执行jsonp传输的话,要这样设置。

    var socket = eio(‘http://localhost:8000‘,{        forceJSONP:true    });

没有设置的话,默认走xhr(ajax).如果跨域的话,会发出options请求,请求服务端同意跨域。这里的细节我不明白。

function XHR(opts){  Polling.call(this, opts);....}/** * Inherits from Polling. */inherit(XHR, Polling);

 XHR继承Polling,Polling又继承Transport。这里调用构造函数时都调用了父类的构造函数,里面就是初始化了一些参数。

这样createTransport()就走完了。接着是transport.open();

Transport.prototype.open = function () {  if (‘closed‘ == this.readyState || ‘‘ == this.readyState) {    this.readyState = ‘opening‘;    this.doOpen();  }  return this;};
Polling.prototype.doOpen = function(){  this.poll();};
Polling.prototype.poll = function(){  debug(‘polling‘);  this.polling = true;  this.doPoll();  this.emit(‘poll‘);};
XHR.prototype.doPoll = function(){  debug(‘xhr poll‘);  var req = this.request();  var self = this;  req.on(‘data‘, function(data){    self.onData(data);  });  req.on(‘error‘, function(err){    self.onError(‘xhr poll error‘, err);  });  this.pollXhr = req;};

doPoll()里对req进行了事件绑定,后面ajax有数据成功返回时会触发上面的data事件,执行onData().this.request()返回的是ajax的封装。

XHR.prototype.request = function(opts){  opts = opts || {};  opts.uri = this.uri();。。。  return new Request(opts);};
function Request(opts){  this.method = opts.method || ‘GET‘;  this.uri = opts.uri; ....  this.create();}
Request.prototype.create = function(){  var opts = { agent: this.agent, xdomain: this.xd, xscheme: this.xs, enablesXDR: this.enablesXDR };  // SSL options for Node.js client  opts.pfx = this.pfx;.........  var xhr = this.xhr = new XMLHttpRequest(opts);  var self = this;  try {    debug(‘xhr open %s: %s‘, this.method, this.uri);    xhr.open(this.method, this.uri, this.async);    if (this.supportsBinary) {      // This has to be done after open because Firefox is stupid      // http://stackoverflow.com/questions/13216903/get-binary-data-with-xmlhttprequest-in-a-firefox-extension      xhr.responseType = ‘arraybuffer‘;    }    if (‘POST‘ == this.method) {      try {        if (this.isBinary) {          xhr.setRequestHeader(‘Content-type‘, ‘application/octet-stream‘);        } else {          xhr.setRequestHeader(‘Content-type‘, ‘text/plain;charset=UTF-8‘);        }      } catch (e) {}    }    // ie6 check    if (‘withCredentials‘ in xhr) {      xhr.withCredentials = true;    }    if (this.hasXDR()) {      xhr.onload = function(){        self.onLoad();      };      xhr.onerror = function(){        self.onError(xhr.responseText);      };    } else {      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);        }      };    }    debug(‘xhr data %s‘, this.data);    xhr.send(this.data);  } catch (e) {    // Need to defer since .create() is called directly fhrom the constructor    // and thus the ‘error‘ event can only be only bound *after* this exception    // occurs.  Therefore, also, we cannot throw here at all.    setTimeout(function() {      self.onError(e);    }, 0);    return;  }};

create()发出ajax请求,这时请求方法是默认的get.前面的文章中说过,服务端会针对不同的请求方法执行不同的策略。这里在创建第一次请求(握手)。

如果响应成功,self.onLoad();,取出数据。onData()

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);  }};
Request.prototype.onSuccess = function(){  this.emit(‘success‘);  this.cleanup();};Request.prototype.onData = function(data){  this.emit(‘data‘, data);  this.onSuccess();};

  this.emit(‘data‘, data);会触发前面doPoll()里面的onData().这里的onData()不是Request里面的onData().

  req.on(‘data‘, function(data){    self.onData(data);  });

注意,这里的emit不是服务端的emit,在源码后面可以看到,它相当于jquery里面的fire(),用来触发自定义事件。

Polling.prototype.onData = http://www.mamicode.com/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);    }  }};

 parser.decodePayload()和服务端里面的一样的作用,就是解析数据,可以简单的认为它把数据解析成关联数值,然后执行回调。

回调里面,先判断是不是握手响应,根据readystate是不是opening.如果是的话,onOpen()

Transport.prototype.onOpen = function () {  this.readyState = ‘open‘;  this.writable = true;  this.emit(‘open‘);};

改变readystate,触发客户端的open事件。

这时readystate是open了,然后是onData()里面的poll().然后执行poll()->doPoll()->request()发出xhr(get方法)请求。

get方法用来做长连接的,当然广播返回的数据也是通过这个get方法。

post方法是向服务端传送心跳的,服务端在一定时间内(pingInterval)收到这个post请求,则认定这个客户端还在。具体的后面会说到。

 回到回调函数里面,self.onPacket(packet);

Transport.prototype.onPacket = function (packet) {  this.emit(‘packet‘, packet);};

packet绑定和服务端一样,

Socket.prototype.setTransport = function(transport){  // set up transport  this.transport = transport;  // set up transport listeners  transport  .on(‘drain‘, function(){    self.onDrain();  })  .on(‘packet‘, function(packet){    self.onPacket(packet);  })  .on(‘error‘, function(e){    self.onError(e);  })  .on(‘close‘, function(){    self.onClose(‘transport close‘);  });};

触发socket.js里面的onPacket()

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);  }};

 收到握手响应后的packet.type=open.至此,我们分析了,从客户端建立到收到握手响应的过程。

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