socket.io客户端分析

最近好像看代码也是零零散散的,因为遇到了比较郁闷的事情,让自己损失挺大的,整个人最近都没啥状态。。

组里面最近要做一点东西,推荐学弟用socket.io来实现服务器向web端推送数据,然后学弟遇到了点问题,其实自己对socket.io的了解也不多,是在读pomelo的源代码的时候才了解它的,知道它是对websocket的更高层的封装,会简单的用一下。。但是个人觉得它用起来还是非常的方便的,于是觉得了解一下它的实现原理。。。

那么先从它的客户端的源代码开始分析,一般情况下我们会用如下的代码来建立一个连接,

//进行websocket的链接
    socket = io.connect(url, {'force new connection': true, reconnect: false});

也就是我们用的是io对象下面的函数,那么我们先来看看io对象的定义吧:

(function (exports, global) {

//用于暴露的作用域,其实是全局,windows.io
  var io = exports;

  io.version = '0.9.6';

  io.protocol = 1;

  io.transports = [];  //所有的transport类型,有什么websocket,flashsocket啥的

  io.j = [];


  io.sockets = {};

//这个函数的作用的是建立与远程服务器的连接,并返回socket,不过这里的socket用namespace又代理了一下,
//这里的namespace其实是与http里面的path相似的概念
  io.connect = function (host, details) {
    var uri = io.util.parseUri(host)   //获取远程服务器的地址
      , uuri
      , socket;

    if (global && global.location) {
      uri.protocol = uri.protocol || global.location.protocol.slice(0, -1);
      uri.host = uri.host || (global.document
        ? global.document.domain : global.location.hostname);
      uri.port = uri.port || global.location.port;
    }

    uuri = io.util.uniqueUri(uri);  //"ws://121.199.40.246:3014"

    var options = {
        host: uri.host
      , secure: 'https' == uri.protocol
      , port: uri.port || ('https' == uri.protocol ? 443 : 80)
      , query: uri.query || ''
    };

    io.util.merge(options, details);
//看看当前这个远程地址是否已经有socket的连接,如果有的话就直接封装一个namespace就可以了
    if (options['force new connection'] || !io.sockets[uuri]) {
      socket = new io.Socket(options);
    }
//这里用于将刚刚建立的连接保存起来,以后可以复用
    if (!options['force new connection'] && socket) {
      io.sockets[uuri] = socket;
    }

    socket = socket || io.sockets[uuri];

    // if path is different from '' or /
    //其实真正返回的是一个namespace,里面封装了发送等方法
    //这里的namespace其实是与建立连接的url的path对应的
    return socket.of(uri.path.length > 1 ? uri.path : '');
  };

})('object' === typeof module ? module.exports : (this.io = {}), this);  //这里可以看出,如果是在浏览器里,那么是在全局定义了io对象

到这里其就能够看到,代码在全局定义了一个io对象,然后它就有一个重要的方法,用于建立一个与远程服务器的连接。。。这里建立的连接其实是通过建立一个Socket对象,这里面可能还有很多其余的东西,在注释里面也已经说的比较清楚,无非就是一个远程连接可能会被多个path复用,也就是socket.io所谓的namespace了,到此,在继续看代码之前,先给出一张图形来综合的介绍一下整个客户端的设计吧:

socket.io客户端分析_第1张图片

上图就是整个socket.io的设计,它用自己定义的socket对最终的websocket或者其余的什么flashsocket进行了一层封装,类似于进行了一层的代理,这种设计在很多地方都能够看到,例如pomelo的connector组件以及netty都是这样子,这里还能看到一个东西就是transport,它其实是设计的一个顶层对象,用相当于抽象出了一些进行通用的方法,要知道其实javascript也是可以实现面向对象编程的。。。。。

那么接下来我们来看看transport的定义吧,这里其实可已经transport看做是一个接口,然后websocket是其的一种具体实现。。

(function (exports, io) {

  //在io下面申明transport
  exports.Transport = Transport;


//这里的socket是外层封装的socket,不是websocket,这里的sessid是与服务器握手之后获取的id
  function Transport (socket, sessid) {
    this.socket = socket;
    this.sessid = sessid;
  };

  io.util.mixin(Transport, io.EventEmitter);


//相当于是接收到了数据,data是接收到的最原始数据
  Transport.prototype.onData = function (data) {
    this.clearCloseTimeout();
    
    if (this.socket.connected || this.socket.connecting || this.socket.reconnecting) {
      this.setCloseTimeout();  //设置超时
    }

    if (data !== '') {
      // todo: we should only do decodePayload for xhr transports
      //用parser对接收到的数据进行解码,将其转换成规定的格式
      var msgs = io.parser.decodePayload(data);

      if (msgs && msgs.length) {
        for (var i = 0, l = msgs.length; i < l; i++) {
          //处理已经解码过后的数据
          this.onPacket(msgs[i]);
        }
      }
    }

    return this;
  };

//处理已经解码的数据,判断接收到的数据的类型,进行相应的处理
  Transport.prototype.onPacket = function (packet) {
    this.socket.setHeartbeatTimeout();
    //心跳类型的数据
    if (packet.type == 'heartbeat') {
      return this.onHeartbeat();
    }
    //连接建立
    if (packet.type == 'connect' && packet.endpoint == '') {
      this.onConnect();
    }
    //错误
    if (packet.type == 'error' && packet.advice == 'reconnect') {
      this.open = false;
    }
    //处理数据
    this.socket.onPacket(packet);

    return this;
  };

  //设置超时,如果规定的时间没有接收到数据,那么表示已经断开了
  //因为socket.io有心跳数据的
  Transport.prototype.setCloseTimeout = function () {
    if (!this.closeTimeout) {
      var self = this;

      this.closeTimeout = setTimeout(function () {
        self.onDisconnect();
      }, this.socket.closeTimeout);
    }
  };

  /**
   * Called when transport disconnects.
   *
   * @api private
   */
   //表示已经断开了,那么进行相应的处理
  Transport.prototype.onDisconnect = function () {
    if (this.close && this.open) this.close();
    this.clearTimeouts();  //清理超时
    this.socket.onDisconnect();   //通知外层的socket,连接已经断开了
    return this;
  };

  /**
   * Called when transport connects
   *
   * @api private
   */
//表示连接建立,通知外层的socket 
  Transport.prototype.onConnect = function () {
    this.socket.onConnect();
    return this;
  }

//关闭超时
  Transport.prototype.clearCloseTimeout = function () {
    if (this.closeTimeout) {
      clearTimeout(this.closeTimeout);
      this.closeTimeout = null;
    }
  };


//清理重开的超时
  Transport.prototype.clearTimeouts = function () {
    this.clearCloseTimeout();

    if (this.reopenTimeout) {
      clearTimeout(this.reopenTimeout);
    }
  };


//这里是用于发送数据,首先要用parser进行编码,这个方法将会在外面的socket的namespace里面调用
  Transport.prototype.packet = function (packet) {
    var data = io.parser.encodePacket(packet);
    this.send(data);
  };


//接收到心跳数据,那么还要给服务器回一个心跳数据
  Transport.prototype.onHeartbeat = function (heartbeat) {
    this.packet({ type: 'heartbeat' });
  };
 
//当open之后会调用的方法,还要通知外面的socket
  Transport.prototype.onOpen = function () {
    this.open = true;
    this.clearCloseTimeout();
    this.socket.onOpen();
  };


//当关闭的时候会调用,而且要通知外层的socket
  Transport.prototype.onClose = function () {
    var self = this;
    this.open = false;
    this.socket.onClose();
    this.onDisconnect();
  };

//用于将url转化为特定的格式
  Transport.prototype.prepareUrl = function () {
    var options = this.socket.options;

    return this.scheme() + '://'
      + options.host + ':' + options.port + '/'
      + options.resource + '/' + io.protocol
      + '/' + this.name + '/' + this.sessid;
  };


//表示当前已经准备好进行连接了,这个方法可能会被具体的实现覆盖
  Transport.prototype.ready = function (socket, fn) {
    fn.call(this);
  };
})(
    'undefined' != typeof io ? io : module.exports
  , 'undefined' != typeof io ? io : module.parent.exports
);

其实transport本身的定义还是很简单的,主要是由三个部分:

(1)接受到数据进行处理,这里还需要用parser进行decode

(2)发送数据,这里需要用parser进行encode

(3)处理心跳数据,socket.io自己定义了心跳数据,如果超时间内没有数据收到或者收不到心跳数据,那么就断开。

好了,到这里transport的定义就这样了,接下来就来看看怎么定义的Socket的吧:

(function (exports, io, global) {

//在io上面申明socket
  exports.Socket = Socket;
//构造函数
  function Socket (options) {
    this.options = {
        port: 80
      , secure: false
      , document: 'document' in global ? document : false
      , resource: 'socket.io'
      , transports: io.transports  //所有的transport类型,有websocket等
      , 'connect timeout': 10000
      , 'try multiple transports': true
      , 'reconnect': true
      , 'reconnection delay': 500
      , 'reconnection limit': Infinity
      , 'reopen delay': 3000
      , 'max reconnection attempts': 10
      , 'sync disconnect on unload': true
      , 'auto connect': true
      , 'flash policy port': 10843
    };

    io.util.merge(this.options, options);  //合并默认配置和用户传进来的配置

    this.connected = false;  //表示当前还没有建立连接
    this.open = false;   //没有打开
    this.connecting = false;
    this.reconnecting = false;
    this.namespaces = {};   //这个socket的所有namespace,就像同一个url可以对应好多path,fjs.com/aa与fjs.com/bb,那么aa与bb就是两个namespace
    this.buffer = [];
    this.doBuffer = false;

    if (this.options['sync disconnect on unload'] &&
        (!this.isXDomain() || io.util.ua.hasCORS)) {
      var self = this;

      io.util.on(global, 'unload', function () {
        self.disconnectSync();
      }, false);
    }

    if (this.options['auto connect']) {
      this.connect();  //建立连接
    }
};

  io.util.mixin(Socket, io.EventEmitter);

//这里是设置名字空间,所谓的namespace其实就是url后面的path,同一个连接可以对应多个path
  Socket.prototype.of = function (name) {
    if (!this.namespaces[name]) {  //如果没有这个namespace,那么创建一个
      //新建一个namespace
      this.namespaces[name] = new io.SocketNamespace(this, name);

      if (name !== '') {//默认的namespace就是空的字符串,
        this.namespaces[name].packet({ type: 'connect' });
      }
    }

    return this.namespaces[name];
  };

//向所有的namespace发送事件,传进来的参数是事件类型
  Socket.prototype.publish = function () {
    this.emit.apply(this, arguments);

    var nsp;
//遍历所有的namespace
    for (var i in this.namespaces) {
      if (this.namespaces.hasOwnProperty(i)) {
        nsp = this.of(i);
        //在当前这个namespace上面触发事件
        nsp.$emit.apply(nsp, arguments);
      }
    }
  };

  function empty () { };

//用于与server进行握手,获取sessionid,心跳间隔,传输方式等
//这里比较神奇的是居然用的是http的get来获取
  Socket.prototype.handshake = function (fn) {
    var self = this
      , options = this.options;

    function complete (data) {
      if (data instanceof Error) {
        self.onError(data.message);
      } else {
        fn.apply(null, data.split(':'));  //sid:heartbeat:close:transports
      }
    };

    var url = [
          'http' + (options.secure ? 's' : '') + ':/'
        , options.host + ':' + options.port
        , options.resource
        , io.protocol
        , io.util.query(this.options.query, 't=' + +new Date)
      ].join('/');

    if (this.isXDomain() && !io.util.ua.hasCORS) {
      var insertAt = document.getElementsByTagName('script')[0]
        , script = document.createElement('script');

      script.src = url + '&jsonp=' + io.j.length;
      insertAt.parentNode.insertBefore(script, insertAt);

      io.j.push(function (data) {
        complete(data);
        script.parentNode.removeChild(script);
      });
    } else {
      var xhr = io.util.request();

      xhr.open('GET', url, true);
      xhr.withCredentials = true;
      xhr.onreadystatechange = function () {
        if (xhr.readyState == 4) {
          xhr.onreadystatechange = empty;

          if (xhr.status == 200) {
            complete(xhr.responseText);
          } else {
            !self.reconnecting && self.onError(xhr.responseText);
          }
        }
      };
      xhr.send(null);
    }
  };

//overide =[ flashsocket,websocket,htmlfile,xhr-polling,jsonp-polling]
  Socket.prototype.getTransport = function (override) {
    var transports = override || this.transports, match;
//遍历当前io定义的所有transport,选取一个
    for (var i = 0, transport; transport = transports[i]; i++) {
      if (io.Transport[transport]
        && io.Transport[transport].check(this)
        && (!this.isXDomain() || io.Transport[transport].xdomainCheck())) {
        //这里用于筛选出transport,传进去的参数是当前这个socket对象,以及sessionid
        return new io.Transport[transport](this, this.sessionid);
      }
    }

    return null;
  };

  Socket.prototype.connect = function (fn) {
    if (this.connecting) {
      return this;
    }

    var self = this;

//transports = flashsocket,websocket,htmlfile,xhr-polling,jsonp-polling
    this.handshake(function (sid, heartbeat, close, transports) {
      self.sessionid = sid;  //sessionid
      self.closeTimeout = close * 1000;  //超时时间
      self.heartbeatTimeout = heartbeat * 1000;
      self.transports = transports ? io.util.intersect(
          transports.split(',')  //将服务器支持的transport转换为数组
        , self.options.transports
      ) : self.options.transports;

      self.setHeartbeatTimeout();   //设置心跳超时

      function connect (transports){
        if (self.transport) self.transport.clearTimeouts();

//这里获取的transport一般情况下都是websocket
        self.transport = self.getTransport(transports);
        if (!self.transport) return self.publish('connect_failed');

        // once the transport is ready
        //如果当前的transport准备好了,那么将会调用回调函数
        self.transport.ready(self, function () {
          self.connecting = true;
          self.publish('connecting', self.transport.name);
          //这里才是真正的建立与服务器的连接
          self.transport.open();

          if (self.options['connect timeout']) {
            //设置连接超时,还可以尝试一下别的transport
            self.connectTimeoutTimer = setTimeout(function () {
              if (!self.connected) {
                self.connecting = false;

                if (self.options['try multiple transports']) {
                  if (!self.remainingTransports) {
                    self.remainingTransports = self.transports.slice(0);
                  }

                  var remaining = self.remainingTransports;

                  while (remaining.length > 0 && remaining.splice(0,1)[0] !=
                         self.transport.name) {}

                    if (remaining.length){
                      connect(remaining);
                    } else {
                      self.publish('connect_failed');
                    }
                }
              }
            }, self.options['connect timeout']);
          }
        });
      }

      connect(self.transports);
      //如果连接建立了,那么需要清理连接超时
      self.once('connect', function (){
        clearTimeout(self.connectTimeoutTimer);

        fn && typeof fn == 'function' && fn();
      });
    });

    return this;
  };

//设置心跳的超时
  Socket.prototype.setHeartbeatTimeout = function () {
    clearTimeout(this.heartbeatTimeoutTimer);

    var self = this;
    this.heartbeatTimeoutTimer = setTimeout(function () {
      self.transport.onClose();
    }, this.heartbeatTimeout);
  };

//用于发送数据,其实这里是调transport进行数据的发送
  Socket.prototype.packet = function (data) {
    if (this.connected && !this.doBuffer) {
      this.transport.packet(data);
    } else {
      this.buffer.push(data);
    }

    return this;
  };

  Socket.prototype.setBuffer = function (v) {
    this.doBuffer = v;

    if (!v && this.connected && this.buffer.length) {
      this.transport.payload(this.buffer);
      this.buffer = [];
    }
  };

//关闭当前的连接,用默认的namespace来发送disconnect数据
  Socket.prototype.disconnect = function () {
    if (this.connected || this.connecting) {
      if (this.open) {
        this.of('').packet({ type: 'disconnect' });
      }

      // handle disconnection immediately
      this.onDisconnect('booted');
    }

    return this;
  };

  Socket.prototype.disconnectSync = function () {
    // ensure disconnection
    var xhr = io.util.request()
      , uri = this.resource + '/' + io.protocol + '/' + this.sessionid;

    xhr.open('GET', uri, true);

    // handle disconnection immediately
    this.onDisconnect('booted');
  };

//是否需要跨域的访问
  Socket.prototype.isXDomain = function () {

    var port = global.location.port ||
      ('https:' == global.location.protocol ? 443 : 80);

    return this.options.host !== global.location.hostname 
      || this.options.port != port;
  };


//表示已经建立了连接,下层的websocket会通知这个方法
  Socket.prototype.onConnect = function () {
    if (!this.connected) {
      this.connected = true;  //表示连接已经建立了
      this.connecting = false;
      if (!this.doBuffer) {
        // make sure to flush the buffer
        this.setBuffer(false);
      }
      this.emit('connect'); //发起connect事件
    }
  };

  Socket.prototype.onOpen = function () {
    this.open = true;
  };


  Socket.prototype.onClose = function () {
    this.open = false;
    clearTimeout(this.heartbeatTimeoutTimer);
  };

//获取相应的namespace来处理数据
  Socket.prototype.onPacket = function (packet) {
    this.of(packet.endpoint).onPacket(packet);
  };

  Socket.prototype.onError = function (err) {
    if (err && err.advice) {
      if (err.advice === 'reconnect' && (this.connected || this.connecting)) {
        this.disconnect();
        if (this.options.reconnect) {
          this.reconnect();
        }
      }
    }

    this.publish('error', err && err.reason ? err.reason : err);
  };

//当底层的连接断开之后会发生的事情
  Socket.prototype.onDisconnect = function (reason) {
    var wasConnected = this.connected
      , wasConnecting = this.connecting;

    this.connected = false;
    this.connecting = false;
    this.open = false;

    if (wasConnected || wasConnecting) {
      this.transport.close();
      this.transport.clearTimeouts();
      if (wasConnected) {
        //出发所有namespace的disconnect事件
        this.publish('disconnect', reason);

        if ('booted' != reason && this.options.reconnect && !this.reconnecting) {
          this.reconnect();
        }
      }
    }
  };

//重新连接
  Socket.prototype.reconnect = function () {
    this.reconnecting = true;
    this.reconnectionAttempts = 0;
    this.reconnectionDelay = this.options['reconnection delay'];

    var self = this
      , maxAttempts = this.options['max reconnection attempts']
      , tryMultiple = this.options['try multiple transports']
      , limit = this.options['reconnection limit'];

    function reset () {
      if (self.connected) {
        for (var i in self.namespaces) {
          if (self.namespaces.hasOwnProperty(i) && '' !== i) {
              self.namespaces[i].packet({ type: 'connect' });
          }
        }
        self.publish('reconnect', self.transport.name, self.reconnectionAttempts);
      }

      clearTimeout(self.reconnectionTimer);

      self.removeListener('connect_failed', maybeReconnect);
      self.removeListener('connect', maybeReconnect);

      self.reconnecting = false;

      delete self.reconnectionAttempts;
      delete self.reconnectionDelay;
      delete self.reconnectionTimer;
      delete self.redoTransports;

      self.options['try multiple transports'] = tryMultiple;
    };

    function maybeReconnect () {
      if (!self.reconnecting) {
        return;
      }

      if (self.connected) {
        return reset();
      };

      if (self.connecting && self.reconnecting) {
        return self.reconnectionTimer = setTimeout(maybeReconnect, 1000);
      }

      if (self.reconnectionAttempts++ >= maxAttempts) {
        if (!self.redoTransports) {
          self.on('connect_failed', maybeReconnect);
          self.options['try multiple transports'] = true;
          self.transport = self.getTransport();
          self.redoTransports = true;
          self.connect();
        } else {
          self.publish('reconnect_failed');
          reset();
        }
      } else {
        if (self.reconnectionDelay < limit) {
          self.reconnectionDelay *= 2; // exponential back off
        }

        self.connect();
        self.publish('reconnecting', self.reconnectionDelay, self.reconnectionAttempts);
        self.reconnectionTimer = setTimeout(maybeReconnect, self.reconnectionDelay);
      }
    };

    this.options['try multiple transports'] = false;
    this.reconnectionTimer = setTimeout(maybeReconnect, this.reconnectionDelay);

    this.on('connect', maybeReconnect);
  };

})(
    'undefined' != typeof io ? io : module.exports
  , 'undefined' != typeof io ? io : module.parent.exports
  , this
);

这里socket的定义够多的吧,一些主要的方法有一些什么用上面的注释都已经说的较为清楚了吧,其实说白了它就是底层的transport与上层的namespace之间的一层代理,具体的发送数据等操作需要调用下层的transport来实现,然后对于transport接收到的数据则将其进行一些处理,然后通知上面的namespace进行处理。。。

好了,就不细说代码了,应该也还算挺简单额,那么接下来来看看前面一直提到过的namespace的定义:

(function (exports, io) {

  exports.SocketNamespace = SocketNamespace;

//socket是上面定义的socket,其实它是对websocket的封装,name是这个namespace的名字,也就是url里面的path
//因为同一个连接其实可以对应多个namespace,也就是好多个path
  function SocketNamespace (socket, name) {
    this.socket = socket;
    this.name = name || '';
    this.flags = {};
    this.json = new Flag(this, 'json');
    this.ackPackets = 0;
    this.acks = {};  //用于记录需要确认的数据的回调函数,当确认消息接收到以后会调用
  };

  io.util.mixin(SocketNamespace, io.EventEmitter);

  SocketNamespace.prototype.$emit = io.EventEmitter.prototype.emit;

  SocketNamespace.prototype.of = function () {
    return this.socket.of.apply(this.socket, arguments);
  };

//这里相当于是调用socket来发送数据,而socket则是调用下面的transport来发送数据
  SocketNamespace.prototype.packet = function (packet) {
    packet.endpoint = this.name;  //也就是当前连接的path
    this.socket.packet(packet);
    this.flags = {};
    return this;
  };

//这里是真正的发送数据
//这里如果提供了回调函数,那么表示这个数据需要确认,当确认消息接收到了以后,回调用这个回调函数
  SocketNamespace.prototype.send = function (data, fn) {
    var packet = {
        type: this.flags.json ? 'json' : 'message'
      , data: data
    };

    if ('function' == typeof fn) {
      packet.id = ++this.ackPackets;   //为这个packet分配id
      packet.ack = true;   //表示这个packet需要确认
      this.acks[packet.id] = fn;     //记录回调函数
    }

    return this.packet(packet);
  };

  //发送数据,采取事件的方式进行发送
  //其实说白了就是另外一种发送数据的方法,用过socket.io的就知道了
  SocketNamespace.prototype.emit = function (name) {
    var args = Array.prototype.slice.call(arguments, 1)
      , lastArg = args[args.length - 1]
      , packet = {
            type: 'event'
          , name: name
        };

    if ('function' == typeof lastArg) {
      packet.id = ++this.ackPackets;
      packet.ack = 'data';
      this.acks[packet.id] = lastArg;
      args = args.slice(0, args.length - 1);
    }

    packet.args = args;

    return this.packet(packet);
  };


//这里用于断开当前namespace的连接,如果是默认namespace或者仅有的namespace的话,那么会断开实际的底层websocket连接
  SocketNamespace.prototype.disconnect = function () {
    if (this.name === '') {
      this.socket.disconnect();
    } else {
      this.packet({ type: 'disconnect' });
      this.$emit('disconnect');
    }

    return this;
  };

//用于处理接收到 的数据,packet
  SocketNamespace.prototype.onPacket = function (packet) {
    var self = this;
//这里用于向服务器发送确认消息
    function ack () {
      self.packet({
          type: 'ack'
        , args: io.util.toArray(arguments)
        , ackId: packet.id
      });
    };
//判断发送过来的数据的类型,然后进行相应的处理
    switch (packet.type) {
      case 'connect':   
        this.$emit('connect');
        break;

      case 'disconnect':
        if (this.name === '') {
          this.socket.onDisconnect(packet.reason || 'booted');
        } else {
          this.$emit('disconnect', packet.reason);
        }
        break;

      case 'message':
      case 'json':
        var params = ['message', packet.data];

        if (packet.ack == 'data') {
          params.push(ack);
        } else if (packet.ack) {
          this.packet({ type: 'ack', ackId: packet.id });
        }

        this.$emit.apply(this, params);
        break;

      case 'event':
        var params = [packet.name].concat(packet.args);

        if (packet.ack == 'data')
          params.push(ack);

        this.$emit.apply(this, params);
        break;

      case 'ack':  //表示是确认消息,
        if (this.acks[packet.ackId]) {
          this.acks[packet.ackId].apply(this, packet.args); //调用消息的回调函数
          delete this.acks[packet.ackId];
        }
        break;

      case 'error':
        if (packet.advice){
          this.socket.onError(packet);
        } else {
          if (packet.reason == 'unauthorized') {
            this.$emit('connect_failed', packet.reason);
          } else {
            this.$emit('error', packet.reason);
          }
        }
        break;
    }
  };

  function Flag (nsp, name) {
    this.namespace = nsp;
    this.name = name;
  };


  Flag.prototype.send = function () {
    this.namespace.flags[this.name] = true;
    this.namespace.send.apply(this.namespace, arguments);
  };

  Flag.prototype.emit = function () {
    this.namespace.flags[this.name] = true;
    this.namespace.emit.apply(this.namespace, arguments);
  };

})(
    'undefined' != typeof io ? io : module.exports
  , 'undefined' != typeof io ? io : module.parent.exports
);

上述namespace的定义也就是socket.io为用户暴露的api(其实个人感觉socket.io自己又定义了一层namespace有那么一点点繁琐了,其实完全不用这个,因为通过用户的代码很容易就能实现相似的功能,而框架自己实现一个namespace机制,让框架不那么轻便),还是很简单,最为主要的功能两个:

(1)下面socket接受的数据会让相应的namespace来处理,那么namespace需要对数据的类型进行分别,然后进行相应的处理,然后触发相应的事件,让用户的代码来处理这些数据

(2)对于用户的一些操作,例如发送数据,断开连接等,调用socket的相应方法来处理。。。

好了,接下来来看看socket.io对websocket的封装吧。。。。这里要强调一下:它继承自transport

(function (exports, io, global) {

  exports.websocket = WS;

//这里直接调用transport的构造函数,这里要记住,websocket直接继承了transport
  function WS (socket) {
    io.Transport.apply(this, arguments);
  };

//这里的websocket需要继承上面的transport
  io.util.inherit(WS, io.Transport);

  WS.prototype.name = 'websocket';

//打开,相当于是建立于服务器的连接
  WS.prototype.open = function () {
    var query = io.util.query(this.socket.options.query)
      , self = this
      , Socket


    if (!Socket) {  //这里将socket指向定义的websocket,因为不同的浏览器可能有不同的实现,不同的名字
      Socket = global.MozWebSocket || global.WebSocket;
    }
//这里就相当于是建立真正的websocket连接了
    this.websocket = new Socket(this.prepareUrl() + query);
    //当下层的websocket打开了
    this.websocket.onopen = function () {
      self.onOpen();
      self.socket.setBuffer(false);
    };
    //当下层的websocket收到了数据
    this.websocket.onmessage = function (ev) {
      self.onData(ev.data);
    };
    this.websocket.onclose = function () {
      self.onClose();
      self.socket.setBuffer(true);
    };
    this.websocket.onerror = function (e) {
      self.onError(e);
    };

    return this;
  };

//其实调用的是内部的websocket来发送
  WS.prototype.send = function (data) {
    this.websocket.send(data);
    return this;
  };

//可以理解为批量处理接收到的数据
  WS.prototype.payload = function (arr) {
    for (var i = 0, l = arr.length; i < l; i++) {
      this.packet(arr[i]);
    }
    return this;
  };
//这几调用底层的websocket来关闭
  WS.prototype.close = function () {
    this.websocket.close();
    return this;
  };
//当有错误发生的时候
  WS.prototype.onError = function (e) {
    this.socket.onError(e);
  };

//wss或者ws
  WS.prototype.scheme = function () {
    return this.socket.options.secure ? 'wss' : 'ws';
  };

//判断浏览器是否支持原生的websocket
  WS.check = function () {
    return ('WebSocket' in global && !('__addTask' in WebSocket))
          || 'MozWebSocket' in global;
  };

  WS.xdomainCheck = function () {
    return true;
  };

//表示当前支持websocket
  io.transports.push('websocket');  //相当于这里的多了一种传输方式,将其保存在io的transports数组中

})(
    'undefined' != typeof io ? io.Transport : module.exports
  , 'undefined' != typeof io ? io : module.parent.exports
  , this
);

这部分代码很简单吧,最重要的也就是open方法了,这里将会真正建立于server端的连接,然后还设置了一些处理函数,例如当数据进来的时候该做什么处理等等。。。


到这里位置,socket.io的client部分的整个设计也都差不太多了,当然我只看了websocket,什么flashsocket什么的我都没有看。。

最上面是namespace,接下来下面有socket,那么在socket里面又包含了transport,对应的也就是websocket。、、、


最后说一下发送的数据的编码格式吧:type:id:path:data  

这里type就是数据的类型,例如event,message,ack等,id就是这个数据的id,如果数据需要确认的话,那么这个id将会有用,path就是那个namespace,data就是data了。。

好了,socket.io的客户端就这样了。。。接下来看看他的服务端吧。。。

你可能感兴趣的:(socket.io客户端分析)