Socket.io-client源碼分析
Socket.io-client source code analysis
根據node_modlues
中socket.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;
};