序
游手好闲,无所事事,危机四伏...轮子虽然多,不是自己的用起来总感觉怪怪的;自己造的,即使七歪八拐,也挺‘快乐’的(^o^)。
socket功能点
大部分我们使用socket的主要功能就是消息实时同步及心跳机制,自己弄之前网上找了一下,发现有挺多好用的socket封装库了,例如vue的vue-socket.io等。
socket 简易封装
- socket具有各种状态(CLOSED,CLOSING,OPEN,CONNECTING)
- 网站协议不同,socket的协议也不同(http://->ws://,https://->wss://)
- 设置socket重连机制(基础重连时间及重连时间策略)
- 设置socket心跳时间(保活机制)
- 设置socket各种回调事件(onopen,onerror,onmessage,onclose,onreconnect)
- 消息数据适配
兼容性及协议判断
websocket在IE10以下不支持,既然能用上socket其实我们已经不在乎这个了-.-,但是为了防止意外出错,做个简单的判断还是挺ok的:/**
* 判断是否支持websocket
*/
static isSupport() {
return /\{\s*\[native code\]\s*\}/.test(WebSocket.toString())
}
这里默认根据当前的网站协议判断ws的协议
window.location.protocol.toLowerCase() === 'https:'
,然后根据协议及参数进行url的拼装:
/**
* 获取websocket完整链接
* @param {string} url 基础websocket URI
*/
static getUrl(url, params = {}, isHttps = false) {
let _path = [url]
let _prefix = 'ws://'
if (isHttps) {
_prefix = 'wss://'
}
_path.unshift(_prefix)
let _query = []
for (let key in params) {
_query.push(`${key}=${params[key]}`)
}
if (url.indexOf('?') > -1) {
_path.push('&')
} else {
_path.push('?')
}
_path.push(_query.join('&'))
return _path.join('')
}
配置参数
根据上面功能点,初略的得出socket的基本参数配置:
<!--默认策略-->
const reconnectStrategy = (count, base) => {
return base * count
}
static defaultConfig = {
// 是否时https连接(默认判断当前URL协议)
isHttps: window.location.protocol.toLowerCase() === 'https:',
// 重连时间step(为0表示不重连)
reconnectTime: 6 * 1000,
// 重连时间策略
reconnectStrategy,
// 心跳时间间隔
heartBeatTime: 60 * 1000,
// 服务器数据适配
serveAdapt: loop,
// onopen回调
onopen: loop,
// onerror回调
onerror: loop,
// onmessage回调
onmessage: loop,
// onclose回调
onclose: loop,
// onreconnect回调
onreconnect: loop,
}
执行(使用)
使用类的写法,这里直接实例化类就可以返回当前ws的实例对象;
loopCount
表示此次重连的次数,重连成功后重置,eventPoll
为简单的事件池用于注册一些简易的事件回调。
constructor(url, params = {}, config = {}) {
// 事件注册池
this.eventPoll = {}
// websocket初始化参数
this.params = params
// 配置项
this.config = { ...WS.defaultConfig, ...config }
this.url = WS.getUrl(url, this.params, this.config.isHttps)
this.loopCount = 0
this._init()
__log(
'%c--> WEB SOCKET <--',
`text-shadow: 0 1px 0 #ccc,0 2px 0 #c9c9c9,
0 3px 0 #bbb,0 4px 0 #b9b9b9,
0 5px 0 #aaa,0 6px 1px rgba(0,0,0,.1),
0 0 5px rgba(0,0,0,.1),0 1px 3px rgba(0,0,0,.3),
0 3px 5px rgba(0,0,0,.2),0 5px 10px rgba(0,0,0,.25),
0 10px 10px rgba(0,0,0,.2),0 20px 20px rgba(0,0,0,.15);
font-size:5em;color: transparent;
`
)
}
实例化后,就会执行ws的_init
方法,进行ws的创建及回调的监听,ws链接成功后通知用户并开启保活_onHeartBeat
,在保活阶段有个小坑(要实时判断当前ws是否属于OPEN状态,否则手动触发_onError
事件。):
/**
* 初始化(注册回调)
*/
async _init() {
if (WS.isSupport()) {
this.ws = new WebSocket(this.url)
this.ws.onopen = this._onOpen.bind(this)
this.ws.onmessage = this._onMessage.bind(this)
this.ws.onclose = this._onClose.bind(this)
this.ws.onerror = this._onError.bind(this)
} else {
log('Your Browser is not support WebSocket.')
}
}
/**
* 心跳函数,状态不为OPEN时触发重连机制(必须在ws open后调用该函数)
*/
async _onHeartBeat() {
await this._sleep(this.config.heartBeatTime)
if (this.ws.readyState === STATE.OPEN) {
log('>>>>ping>>>>')
this.ws.send('ping')
this._onHeartBeat()
} else {
this._onError(new Event('ws_close'))
}
}
/**
* ws开启回调
*/
_onOpen() {
log('websocket已打开,开启心跳')
this.loopCount = 0
this.config.onopen(this.ws)
this._onHeartBeat()
}
/**
* 消息监听回调
* @param {Object} msg 服务器推送消息
*/
_onMessage(msg) {
try {
let _data = JSON.parse(msg.data)
this.config.onmessage(_data)
let _events = []
// code码事件
let _code = Number(_data.code)
let _codeEvents = this.eventPoll[_code]
if (_codeEvents) {
_events.push(..._codeEvents)
}
for (let _cb of _events) {
_cb(_data)
}
} catch (error) {
log('数据解析错误', error)
}
}
/**
* ws关闭回调
*/
_onClose(e) {
log(`>>>>Socket已关闭>>>>`)
this.config.onclose(e)
}
/**
* ws出错回调
*/
_onError(e) {
this.config.onerror(e)
if (!this.config.reconnectTime) {
return
}
let _delayTime = this.config.reconnectStrategy(this.loopCount++, this.config.reconnectTime)
log(`>>>>${_delayTime / 1000}s后尝试重新连接>>>>`)
this.config.onreconnect(_delayTime, this.loopCount)
this._sleep(_delayTime).then(() => {
log(`>>>>第${this.loopCount}次重新连接>>>>`)
this._init()
})
}
- 在
_onError
回调中,判断reconnectTime
是否大于0(0表示不会重连),然后根据当前轮次及重连基础时间得出当前过多久开始重连_delayTime
(重连就是简单的重新初始化ws-即重新调用_init
) 以下是eventPoll的简易实现(类型及回调):
/**
* 订阅消息回调
* @param {Any} type 事件码
* @param {Function} cb 事件回调
* @returns {Function} 取消订阅事件
*/
subscribe(type, cb) {
let _poll = this.eventPoll[type] || (this.eventPoll[type] = [])
let _index = _poll.indexOf(cb)
if (_index === -1) {
_poll.push(cb)
_index = _poll.length - 1
}
return () => {
let _i=_poll.findIndex(p=>p===cb)
if(_i>-1){
_poll.splice(_i, 1)
return true
}
return false
}
}
- 最后在退出当前页面时要销毁当前ws(这里设置
reconnectTime
为0是为了关闭_onError
中不断重连的机制判断):
/**
* 关闭重连,销毁
*/
closeReconnect() {
this.config.reconnectTime = 0
this.ws.close()
}
安装使用
npm使用:
npm i '@qiangkun/sdk'
import { Easysocket } from '@qiangkun/sdk'
const EVENT_MAP = {
201: 'sw_eva_pack_update',
202: 'sw_eva_video_update',
203: 'sw_eva_nego_update',
204: 'sw_eva_wait_update',
'5563': 'token_expire',
'3002': 'login_other'
}
let sw = new Easysocket(
'xxx',
{
app_id: 'app_agency',
access_token: getToken()
},
{
onerror: (e) => {
console.log('error',e)
},
onclose: () => {
console.log('close')
}
}
)
for (let code in EVENT_MAP) {
sw.subscribe(code, msg => {
<!--使用vue事件机制-->
bus.$emit(EVENT_MAP[code], msg)
})
}
结语
自己倒腾,锅自己认,自己背--
不间断的拨弄原生js其实也算是对自己能力提升有所帮助,拨弄不了后面反正还有一堆的库使用,这样边学习边积累还是不错的~_~。