最近好像看代码也是零零散散的,因为遇到了比较郁闷的事情,让自己损失挺大的,整个人最近都没啥状态。。
组里面最近要做一点东西,推荐学弟用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 = 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的客户端就这样了。。。接下来看看他的服务端吧。。。