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的设计,它用自己定义的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 = fu
  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值