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