socket.io-client源码分析——建⽴socket连接
介绍
socket.io是⼀种⽤于服务端和客户端的双向通信的js库,提供了长轮询和websocket这两种实现⽅式socket.io-client是其在客户端的实现。socket.io-client通过⽅法on监听来⾃服务器的通信,通过⽅法emit向服务器传递信息。
socket.io-client对外暴露相关api,处理与外界的交互,对外界数据通过socket.io-parser库解析成socket.io-protocol规定的格式数据源,通过底层引擎库engine.io-client传输⾄服务器。同时监听engine.io-client从服务器获取的返回信息。
架构分析
socket.io-client库内部分为两个类,Manager和Socket。分别连接内部引擎和外部沟通,互相之间通讯⽅式参照node.js中Event Emitter的⽅式。
Manager类负责对整个库的管理和处理,对Socket传来的信息编码后传递到engine.io-client,处理socket的错误和异常。当收到engine.io-client的信息时,解码后传递给Socket。
Socket类负责外部的api,暴露emit、on、once、disconnect等⽅法,对外部的信息进⾏处理传递到Manager。
源码分析
上图是socket.io-client建⽴连接的流程图,可以看出来是⽐较简单的。初始Manager类和Socket类,在Manager初始化的同时,初始化引擎,引擎负责与服务器通知,Manager负责与引擎通信,将通信内容通过Socket类传递到外部。
接下来我们看源码:
function lookup(uri, opts){
if(typeof uri ==='object'){
opts = uri;
uri = undefined;
}
/
/ 初始化参数,这⾥只保留了必要的参数
opts = opts ||{};
let parsed =url(uri);// 将uri字符串转换为包含host、port、query等字段的对象
let source = parsed.source;// 请求的原地址,即uri
let newConnection =true;
// 初始化Manager类,⽬前只做了新连接的初始化
let io;
if(newConnection ===true){
io =Manager(source, opts);
}else{
// 这⾥对Manage实例进⾏缓存,这⾥不做介绍
}
if(parsed.query &&!opts.query){
opts.query = parsed.query;
}
// 初始化Socket类
return io.socket(parsed.path, opts);
}
上⾯是socket.io-client的⼊⼝函数,只做了三件事,初始化参数、初始化Manager类实例、初始化Socket类实例,然后向外暴露Socket实例。
我们按顺序逐步分析,调⽤io = Manager(source, opts);后发⽣了:
// Manager类的构造函数
function Manager(uri, opts){
if(!(this instanceof Manager))return new Manager(uri, opts);
if(typeof uri ==='object'){
opts = uri;
uri = undefined;
}
opts = opts ||{};
opts.path = opts.path ||'/socket.io';// 这⾥是真实的请求路径,uri上的请求路径实际上是socket的命名空间,这⾥需要加以区分
this.nsps ={};// 不同命名空间的socket的缓存
this.subs =[];// 事件或定时器销毁的缓存列表
websocket和socketthis.opts = opts;// 参数
this.randomizationFactor(opts.randomizationFactor ||0.5);// 随机数因⼦,作为间隔时间随机增长的参数之⼀
// backoff实例,⽤户记录和刷新间隔时间和重连次数
this.backoff =new Backoff({
ectionDelay(),
ectionDelayMax(),
jitter:this.randomizationFactor()
});
this.timeout(null== opts.timeout ?20000: opts.timeout);// 连接超时时长
this.uri = uri;// 链接地址
this.lastPing =null;// 最近⼀次⼼跳检测的时间
this.packetBuffer =[];// 数据包缓冲区
var _parser = opts.parser || parser;// 编解码⼯具,默认使⽤基于socket.io-protocol的socket.io-parser库
this.decoder =new _parser.Decoder();// 解码器
this.autoConnect = opts.autoConnect !==false;// 是否⾃动连接,默认是
if(this.autoConnect)this.open();// 如果⾃动连接,则开始连接
}
上⾯是Manager类的构造函数,调⽤后会返回⼀个Manager实例,函数内主要是对⼀些重要的属性进⾏初始化,以及进⾏⾃动连接服务器(默认情况下)。
Manager.prototype.open =
t=function(fn, opts){
// 如果当前状态为open(已连接)或opening(连接中),则不需要重复连接
if(~adyState.indexOf('open')return this;
var socket =ine;
var self =this;
this.skipReconnect =false;// 是否跳过重连
// 监听engine的open实践,出发此事件表明连接已建⽴
var openSub =on(socket,'open',function(){
fn &&fn();// 有回调则触发回调
});
// 监听engine的error事件,连接失败时出发此事件
var errorSub =on(socket,'error',function(data){
self.cleanup();// 清除缓存
if(fn){// 如果有回调函数则调⽤回调函数
var err =new Error('Connection error');
err.data = data;
fn(err);
}else{// 没有则判断是否需要重连,这⾥只在第⼀次⾃动连接时发⽣
// Only do this if there is no fn to handle the error
self.maybeReconnectOnOpen();
}
});
// 如果没有取消超时设置,则默认有个连接超时处理
if(false!==this._timeout){
var timeout =this._timeout;
// 设置定时器,超过预定时间则触发超时
var timer =setTimeout(function(){
openSub.destroy();// 销毁open事件的监听回调
socket.close();// 关闭socket
}, timeout);
// 将定时任务的销毁推⼊订阅缓存
this.subs.push({
destroy:function(){
clearTimeout(timer);
}
});
}
this.subs.push(openSub);// 将open事件的销毁推⼊订阅缓存
this.subs.push(errorSub);// 将error事件的销毁推⼊订阅缓存
return this;
}
在这⾥,做了以下五个步骤:1、初始化引擎;2、修改状态为opening;3、监听引擎的open和error事件;4、默认进⾏超时处理;
5、将监听事件和定时器的销毁⽅法存⼊缓存。
eio是socket.io-client的引擎(即engine.io-client)的部分,负责核⼼的连接控制、请求发送与相应、⼼跳检测,使socket.io-client 能更多地关注与外部的沟通。engine.io-client的原理也将会在后续⽂章进⾏探讨。
open函数执⾏完后,Manager实例便⽣成了,接下来就是Socket类的初始化实例过程。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论