Socket.io-client源碼分析

Socket.io-client源碼分析

Socket.io-client source code analysis

根據node_modluessocket.io-client的文件結構,大概畫了張UML圖。

在這裏插入圖片描述

從圖片中可以清楚地看到模塊中lib文件夾下各文件的依賴關係。

Note:帶箭頭的虛線表示依賴關係,如socket.js需要依賴on.js ,則箭頭指向on.js,即依賴對象指向被依賴的對象。

下面就對lib目錄下文件進行分析


on.js

/**
 * Helper for subscriptions.
 *
 * @param {Object|EventEmitter} obj with `Emitter` mixin or `EventEmitter`
 * @param {String} event name
 * @param {Function} callback
 * @api public
 */

function on (obj, ev, fn) {
  obj.on(ev, fn);
  return {
    destroy: function () {
      obj.removeListener(ev, fn);
    }
  };
}
/**
 * Emitter是一個模塊,實現了mixin模式,obj = Emitter(obj) 或者 obj = new Emitter;
 * obj.on(ev, fn) 是給事件ev註冊了一個函數
 * 函數返回一個帶有去除監聽事件功能的對象。

url.js

/**
 * URL parser.
 *
 * @param {String} url
 * @param {Object} An object meant to mimic window.location.
 *                 Defaults to window.location.
 * @api public
 */

function url (uri, loc) {
  var obj = uri;

  // loc 是一個模仿window.location的對象
  // 默認是window.location
  loc = loc || (typeof location !== 'undefined' && location);
  //如果uri不存在的話,默認值爲當前瀏覽器地址的協議加上主機名。
  //e.g 瀏覽器地址欄爲https://socket.io/docs/client-api/#socket-disconnected
  // 則uri = https://socket.io
  if (null == uri) uri = loc.protocol + '//' + loc.host;

  // relative path support
  // 路徑支持
  // '//socket.io' => https://socket.io
  // '/docs' => socket.io/docs
  if ('string' === typeof uri) {
    if ('/' === uri.charAt(0)) {
      if ('/' === uri.charAt(1)) {
        uri = loc.protocol + uri;
      } else {
        uri = loc.host + uri;
      }
    }

    //不使用超文本安全傳輸協議或者websocket security
    if (!/^(https?|wss?):\/\//.test(uri)) {
      debug('protocol-less url %s', uri);
      if ('undefined' !== typeof loc) {
        //loc爲空的情況下,使用當前瀏覽器地址使用協議
        uri = loc.protocol + '//' + uri;
      } else {
        //否則使用超文本安全傳輸協議
        uri = 'https://' + uri;
      }
    }

    // parse
    debug('parse %s', uri);
    // 使用engine.io-client解析uri
    obj = parseuri(uri);
  }

  // 確保使用的是默認端口
  if (!obj.port) {
    if (/^(http|ws)$/.test(obj.protocol)) {
      obj.port = '80';
    } else if (/^(http|ws)s$/.test(obj.protocol)) {
      obj.port = '443';
    }
  }

  obj.path = obj.path || '/';

  var ipv6 = obj.host.indexOf(':') !== -1;
  var host = ipv6 ? '[' + obj.host + ']' : obj.host;

  // define unique id
  obj.id = obj.protocol + '://' + host + ':' + obj.port;
  // define href
  obj.href = obj.protocol + '://' + host + (loc && loc.port === obj.port ? '' : (':' + obj.port));

  return obj;
}

socket.js

    /**
     * module dependencies
     **/
    var parser = require('socket.io-parser');
    var Emitter = require('component-emitter');
    var toArray = require('to-array');
    var on = require('./on');
    var bind = require('component-bind');
    var debug = require('debug')('socket.io-client:socket');
    var parseqs = require('parseqs');
    var hasBin = require('has-binary2');

先分析一些模塊及其作用

module name function
socket.io-parser encoder&decoder
component-emitter Event emitter component
component-bind Function binding utility
to-array Turn an array like into an array
parseqs Provides methods for converting an object into string representation, and vice versa
has-binary2 test if an object contains binary data
var events = {
  connect: 1,
  connect_error: 1,
  connect_timeout: 1,
  connecting: 1,
  disconnect: 1,
  error: 1,
  reconnect: 1,
  reconnect_attempt: 1,
  reconnect_failed: 1,
  reconnect_error: 1,
  reconnecting: 1,
  ping: 1,
  pong: 1
};

上面列出的事件是無法被用戶emit的,是私有事件名。

    /**
     * `Socket` constructor.
     *
     * @api public
     */

    function Socket (io, nsp, opts) {
        this.io = io;
        this.nsp = nsp;
        this.json = this; // compat
        this.ids = 0;
        this.acks = {};
        this.receiveBuffer = [];
        this.sendBuffer = [];
        this.connected = false;
        this.disconnected = true;
        this.flags = {};
        if (opts && opts.query) {
            this.query = opts.query;
        }
        if (this.io.autoConnect) this.open();
    }

socket的構造函數。


API 解析

Socket#open()

    /**
     * "Opens" the socket. 手動打開連接
     *
     * @api public
     */

    Socket.prototype.open =
    Socket.prototype.connect = function () {
        if (this.connected) return this;

        this.subEvents();
        this.io.open(); // ensure open
        if ('open' === this.io.readyState) this.onopen();
        this.emit('connecting');
        return this;
    };
    /**
     * 連接之前先檢查實例是否已經連接了,連接的話直接發回實例本身。如果沒有則需要爲本身訂閱open、close、packet事件。發射connectting事件。
     **/

    //除了上面之外,我想分析一下subeEvents函數
    /**
     * Subscribe to open, close and packet events
     *
     * @api private
     */

    Socket.prototype.subEvents = function () {
        if (this.subs) return;

        var io = this.io;
        this.subs = [
            on(io, 'open', bind(this, 'onopen')),
            on(io, 'packet', bind(this, 'onpacket')),
            on(io, 'close', bind(this, 'onclose'))
        ];
    };
    /**
     * 上面有對on函數的分析。這裏給socket對象本身註冊了open、packet、close事件的監聽的函數。這三個事件分別對應了Socket.proptotype.onopen、Socket.proptotype.onpacket、Socket.proptotype.onclose。 當我們嘗試手動打開連接的時候就會調用subEvents函數。
     * */

    //如果客戶端或者服務端被強制斷開連接時,就會調用destroy函數。下面的代碼中會做一個for循環,在連接斷開時,移除對open、packet、close事件的監聽。
    /**
     * Called upon forced client/server side disconnections,
     * this method ensures the manager stops tracking us and
     * that reconnections don't get triggered for this.
     *
     * @api private.
     */

    Socket.prototype.destroy = function () {
        if (this.subs) {
            // clean subscriptions to avoid reconnections
            for (var i = 0; i < this.subs.length; i++) {
            this.subs[i].destroy();
            }
            this.subs = null;
        }

        this.io.destroy(this);
    };

Socket#send()

    /**
     * Sends a `message` event.
     *
     * @return {Socket} self
     * @api public
     */

    Socket.prototype.send = function () {
        var args = toArray(arguments);
        args.unshift('message');
        this.emit.apply(this, args);
        return this;
    };

    /**
     * 參數被轉化成數組. 並且在數組的首位上添加了'message'。
     * 然後調用emit方法將數組發射出去。
     **/

Socket#emit()

    /**
     * Override `emit`.
     * If the event is in `events`, it's emitted normally.
     *
     * @param {String} event name
     * @return {Socket} self
     * @api public
     */

    Socket.prototype.emit = function (ev) {
        //先檢查一下事件名是否是私有的, 具體的私有事件名參考上面event對象 
        //是私有事件的話直接發射。
        if (events.hasOwnProperty(ev)) {
            emit.apply(this, arguments);
            return this;
        }

        //如果不是私有事件的話,則將要發送的arguments封裝成數據包packet發送。
        var args = toArray(arguments);
        var packet = {
            type: (this.flags.binary !== undefined ? this.flags.binary : hasBin(args)) ? parser.BINARY_EVENT : parser.EVENT,
            data: args
        };

        packet.options = {};
        packet.options.compress = !this.flags || false !== this.flags.compress;

        // event ack callback
        //emit()函數參數中是否有ack回調
        if ('function' === typeof args[args.length - 1]) {
            debug('emitting packet with ack id %d', this.ids);
            //根據傳輸的packet的id號作爲key,添加ack回調作爲value
            this.acks[this.ids] = args.pop();
            packet.id = this.ids++;
        }

        if (this.connected) {
            this.packet(packet);
        } else {
            this.sendBuffer.push(packet);
        }

        this.flags = {};

        return this;
    };

Note: 什麼是packet?我在socket.io-protocal中找到了這樣的定義 。

 Packet

  Each packet is represented as a vanilla `Object` with a `nsp` key that indicates what namespace it belongs to (see "Multiplexing") and a `type` key that can be one of the following:

其中type的所有值爲:

  • Packet#CONNECT (0)
  • Packet#DISCONNECT (1)
  • Packet#EVENT (2)
  • Packet#ACK (3)
  • Packet#ERROR (4)
  • Packet#BINARY_EVENT (5)
  • Packet#BINARY_ACK (6)

更加具體的內容請參考socket.io-protocol

Socket#compress()

    /**
     * Sets the compress flag.設置compress標誌位
     *
     * @param {Boolean} if `true`, compresses the sending data
     * @return {Socket} self
     * @api public
     */

    Socket.prototype.compress = function (compress) {
        this.flags.compress = compress;
        return this;
    };

Socket#compress()

    /**
     * Sets the binary flag,設置binary標誌位
     *
     * @param {Boolean} whether the emitted data contains binary
     * @return {Socket} self
     * @api public
     */

    Socket.prototype.binary = function (binary) {
        this.flags.binary = binary;
        return this;
    };
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章