用TypeScript实现对WebSocket的封装-Part-2

在驱动接口声明完成后,实现功能部分会比较繁复,需要考虑保活/断线重连/消息丢失重发/消息去重,等大部分弱网络下的问题,这是为了方便上层的调用,上层无需关心底层驱动的实现.

实现`chat-ws.ts`:

“`typescript
///<reference path="./chat-ws.d.ts" />
import { IntervalTimer, ETimerEventType as ETETP } from './chat-timer';
@Injectable
export class ChatWSProvider implements IChatWSProvider {
construtor(){
//…
}
private logger: ILogger = null;
private remoteAddr: string;
private webSocket: WebSocket = null;
private isManualDisconnect: boolean = false;
private wsStateDef = { CONNECTING: 0, OPEN: 1, CLOSING: 2, CLOSED: 3 }; //WebSocket内置连接状态

private readonly reconnMaxSpan: number = 40*1000 //重连最大间隔
private innerTimer: IntervalTimer = null; //内部定时器

/** 定义定时器的被调用时间间隔 */
private readonly timerSpace = {
HeartBeat: 240 * 1000, //心跳保活间隔4分钟,因为一个连接长期休眠定然会被回收,而不同运营商的NAT超时阈值不同,不同网关也可能存在不同NAT超时回收阈值,国内平均是5分钟,可使用微信延迟心跳测试算法计算最大心跳,这里为方便使用固定值.
Reconnect: 4 * 1000, //初始重连间隔4秒,随后重连每次递增2秒间隔至最大间隔,直至恢复连接
MsgSendTimeout: 300 * 1000 //消息超时检测值5分钟
};
priate reconnCurrSpan: number = this.timerSpace.Reconnect; //当前重连初始值.
/** 事件集合,须在连接前赋值初始化 */
private event: IWebSocketEvent = {
onLogin: null,
onLogout: null,
onRecive: null,
onError: null,
};

private initTimer():void{
if(this.innerTimer == null){
this.innerTimer = new IntervalTimer(this.logger);
}
//注册心跳处理事件
this.innerTimer.Register(ETETP.HeartBeat,this.timerSpace.Reconnect,(nowTime: number, timeSpan: number): boolean => {
//具体实现需要根据实际业务策略.
},true);

//注册消息超时检测处理事件
this.innerTimer.Registry(ETETP.MsgSendTimeout,this.timerSpance.MsgSendTimeout,(nowTime: number, timeSpan: number): boolean => {
//具体实现需要根据实际业务策略.
},true);

//注册重连处理事件
this.innerTimer.Registry(ETimerEvent.Reconnect, this.timerSpace.Reconnect, (nowTime: number, timeSpan: number): boolean => {
//具体实现需要根据实际业务策略.
},true);

this.innerTimer.Start();
}

private initWsHandler():void{
this.webSocket.onclose = (closeEv: CloseEvent)=>{
this.innerTimer && this.innerTimer.SetStatus(ETETP.HeartBeat,false);
this.innerTimer && this.innerTimer.SetStatus(ETETP.MsgSendTimeout,true);
if(this.isManualDisconnect){
this.innerTimer && this.innerTimer.SetStatus(ETETP.Reconnect, false);
}else{
this.innerTimer && this.innerTimer.SetStatus(ETETP.Reconnect);
this.event.onError && this.event.onError({/** */});
}
//…具体业务实现
};
this.webSocket.onerror = (errorEv: Event)=>{
//…具体业务实现
this.innerTimer && this.innerTimer.SetStatus(ETETP.Reconnect);
};
this.webSocket.onopen = (openEv: Event)=>{
this.isManualDisconnect = false;
this.webSocket.onerror = ()=>{ //这是设置异常断线后的错误回调事件.
!this.isManualDisconnect && this.innerTimer && this.innerTimer.SetStatus(ETETP.Reconnect);
};
//…具体业务实现
};
this.webSocket.onmessage = async (msgEV: MessageEvent)=>{
//…具体业务实现
};
}

public verifyRemoteAddr(addr:string):boolean{
//…验证上层给的服务器地址端口的合法性
}

public initEvHandler(onLogin: FnOnLoginCallback, onLogout: FnOnLogoutCallback, onRecive: FnOnReciveCallback,onError: FnOnErrorCallback):void{
this.event.onLogin = onLogin;
this.event.onLogout = onLogout;
this.event.onRecive = onRecive;
this.event.onError = onError;
}
public get isConnected():boolean{
if(!this.webSocket) return false;
return this.webSocket.readyState == this.wsStateDef.OPEN;
}
public conncet(loginInfo?: TSendPkg[]):boolean{
if(this.isConnected){
this.logger.warn("ws was connected, now signin…");
this.online(); //如果连接已建立,则调用上线登录函数
return true;
}
this.webSocket = null; //每次重连都必须将连接重置
try {
if("WebSocket" in window) {
try{
if(this.remoteAddr.startWith("ws"))
this.webSocket = new WebSocket(this.remoteAddr);
else
return false;
this.webSocket.binaryType = "arraybuffer";
} catch(e){
this.logger.error(e);
}
}else{
this.logger.error("The Bowser not support websocket protocol.");
this.event.onError && this.event.onError({
//…
});
return false;
}
if(this.webSocket.readyState != this.wsStateDef.CONNECTING || this.webSocket.readyState != this.wsStateDef.OPEN){
this.logger.warn("ws connect failed. WebSocket readyState: " + this.webSocket.readyState);
return false;
}
} catch(e){
this.logger.error("ws init failed.");
this.event.onError && this.event.onError({
//…
});
return false;
}
this.isManualDisconnect = false;
//…
this.initTimer();
this.initWsHandler();
return true;
}

public disconnect():void{
//…具体业务实现

this.innerTimer && this.innerTimer.Dispose();
if(!this.isConnected) return;
if(this.webSocket != null) this.webSocket.close();
this.isManualDisconnect = true;
}

public sendMsg(msg?:TMsgPkg):boolean{
//…具体业务实现
}
}
“`

其实一个聊天底层驱动封装的代码实现并不难,难的是需要考虑到各种情况,并作出相应处理的动作.