对原生websocket
的使用封装:
- 支持发生错误后的自动重连
- 支持
on
与emit
方法 - 暴露
open
,close
,error
事件, 可自定义后续业务逻辑 - 支持单例模式
封装MySocket
/*
* @Author: shuai
* @Date: 2021-02-21 09:58:13
* @LastEditors: shuai
* @LastEditTime: 2021-02-21 12:04:38
* @Description: file content
*/
class MySocket {
static singleObj = null;
static getObj = wsUrl => {
if (!MySocket.singleObj) MySocket.singleObj = new MySocket(wsUrl);
return MySocket.singleObj;
};
static RECONNECT_NUM = 5; // 最多重试次数, 为0表示不重连
static RECONNECT_DELAY = 3000 // 断线重连间隔
constructor(wsUrl) {
this.socket = null; // 原生websocket连接
this.wsUrl = wsUrl; // ws地址
this.isSupport = true; // 是否支持ws
this.recNum = 0; // 重连次数
this.eventHandle = {}
if (typeof WebSocket === "undefined") {
this.isSupport = false;
alert("你的浏览器不支持WebSocket!请更新浏览器!");
}
}
// 根据与后台协商的协议解析数据
messageToObject(messageData) {
const messageObj = typeof messageData === 'string' && JSON.parse(messageData)
const { eventName, data } = messageObj
return { eventName, data }
}
// 根据与后台协商的协议解析数据
messageObjToString(eventName, dataObj) {
return JSON.stringify({ eventName, data: dataObj })
}
connect() {
if (!this.isSupport) return Promise.reject("Do not support Websocket!");
if (this.socket) return Promise.resolve(this);
console.log('connect...')
const socket = new WebSocket(this.wsUrl);
socket.onclose = e => {
console.log("close触发", e);
// 默认规定可监听'wsClose'事件
this.eventHandle.wsClose && this.eventHandle.wsClose(e)
this.socket = null;
// 1000 表示正常关闭; 无论为何目的而创建, 该链接都已成功完成任务。
if (e.code !== 1000 && this.recNum < MySocket.RECONNECT_NUM) {
setTimeout(() => {
this.connect();
this.recNum++;
console.warn('断线自动重连', this.recNum)
if (this.recNum >= MySocket.RECONNECT_NUM) console.error('重连次数达到上限!')
}, MySocket.RECONNECT_DELAY);
}
};
socket.onmessage = websocketData => {
const messageData = websocketData.data
const {eventName, data} = this.messageToObject(messageData)
if (this.eventHandle[eventName]) this.eventHandle[eventName](data)
};
return new Promise((resolve, reject) => {
socket.onopen = () => {
console.log(this.wsUrl, "connect successfully!");
this.recNum = 0; // 重置重连次数
this.socket = socket;
// 默认规定可监听'wsOpen'事件
this.eventHandle.wsOpen && this.eventHandle.wsOpen()
resolve(this);
};
socket.onerror = e => {
console.log(this.wsUrl, "connection failed!", e);
this.socket = null;
reject(e);
// 默认规定可监听'wsError'事件
this.eventHandle.wsError && this.eventHandle.wsError(e)
};
});
}
on(eventName, callBack = () => {}) {
this.eventHandle[eventName] = callBack
}
off(eventName) {
this.eventHandle[eventName] = null
}
emit(eventName, dataObj) {
if (!this.socket || this.socket.readyState !== 1) {
console.error(eventName, 'emit失败!')
return
}
this.socket.send(this.messageObjToString(eventName,dataObj))
}
disconnect() {
this.socket && this.socket.close(1000)
this.socket = null
}
}
const socket = MySocket.getObj("ws://127.0.0.1:3000");
// 可以暴露整个MySocket类, 而不是实例
export default socket;
测试使用
import HelloWorld from './components/HelloWorld.vue'
import { onMounted } from 'vue'
import mySocket from '@/utils/MyWebsocket'
export default {
name: 'App',
components: {
HelloWorld
},
setup() {
onMounted(() => {
console.log('onMounted')
mySocket.connect().then(socket => {
socket.emit('testFromClient', {a: 'bbb'})
socket.on('test', (data) => {
console.log('客户端收到test事件', data)
})
socket.on('wsOpen', () => {
console.log('监听到wsOpen')
})
socket.on('wsError', () => {
console.log('监听到wsError')
})
socket.on('wsClose', () => {
console.log('监听到wsClose')
})
// setTimeout(() => {
// console.log('客户端主动断开连接')
// socket.disconnect()
// }, 5000)
}).catch(err => {
console.log('mySocket错误',err)
})
})
}
}
更新, 借助 tiny-emitter
使用tiny-emiter
, 调用更加灵活, 尤其是处理异常时.
/*
* @Author: shuai
* @Date: 2021-02-21 09:58:13
* @LastEditors: shuai
* @LastEditTime: 2021-03-05 14:38:01
* @Description: file content
*/
import Emitter from 'tiny-emitter'
class MySocket extends Emitter {
static singleObj = null;
static getObj = wsUrl => {
if (!MySocket.singleObj) MySocket.singleObj = new MySocket(wsUrl)
return MySocket.singleObj
};
static RECONNECT_NUM = 5; // 最多重试次数, 为0表示不重连
static RECONNECT_DELAY = 3000 // 断线重连间隔
constructor(wsUrl) {
super()
this.socket = null // 原生websocket连接
this.connectResult = null
this.wsUrl = wsUrl // ws地址
this.isSupport = true // 是否支持ws
this.recNum = 0 // 重连次数
this.eventHandle = {}
if (typeof WebSocket === 'undefined') {
this.isSupport = false
alert('你的浏览器不支持WebSocket!请更新浏览器!')
}
this.connect()
}
// 与后台定义的通讯协议
messageToObject(messageData) {
const messageObj = typeof messageData === 'string' && JSON.parse(messageData)
const { metadata: { event: eventName }, data } = messageObj
if (!eventName) {
console.error('数据格式错误!')
return
}
return { eventName, data }
}
// 与后台定义的通讯协议
messageObjToString(eventName, dataObj) {
return JSON.stringify({ metadata: { event: eventName, timestamp: new Date().getTime() }, data: dataObj })
}
connect() {
if (!this.isSupport) return Promise.reject('Do not support Websocket!')
if (this.socket) return Promise.resolve(this)
console.log('connect...')
const socket = new WebSocket(this.wsUrl)
socket.onclose = e => {
console.log('close触发', e)
// 默认规定可监听'wsClose'事件
this.emit('wsClose', e)
this.socket = null
// 1000 表示正常关闭; 无论为何目的而创建, 该链接都已成功完成任务。
if (e.code !== 1000 && this.recNum < MySocket.RECONNECT_NUM) {
setTimeout(() => {
this.connect()
this.recNum++
console.warn('断线自动重连', this.recNum)
if (this.recNum >= MySocket.RECONNECT_NUM) {
this.emit('wsRecFail')
console.error('重连次数达到上限!')
}
}, MySocket.RECONNECT_DELAY)
}
}
socket.onmessage = websocketData => {
const messageData = websocketData.data
const { eventName, data } = this.messageToObject(messageData)
this.emit(eventName, data)
}
this.connectResult = new Promise((resolve, reject) => {
socket.onopen = () => {
console.log(this.wsUrl, 'connect successfully!')
this.recNum = 0 // 重置重连次数
this.socket = socket
// 默认规定可监听'wsOpen'事件
this.emit('wsOpen')
resolve(this)
}
socket.onerror = e => {
console.log(this.wsUrl, 'connection failed!', e)
this.socket = null
reject(e)
// 默认规定可监听'wsError'事件
this.emit('wsError', e)
}
}).catch((err) => {
console.log('捕获到ws连接报错!', err)
// throw err
})
return this.connectResult
}
async send(eventName, dataObj) {
if (this.getConnectResult() === null) await new Promise((resolve) => setTimeout(resolve, 500))
// console.log('连接结果', this.getConnectResult())
// 这里必须要求, 刷新页面时会直接请求getInfo,此时可能还未建立连接
try {
await this.getConnectResult()
this.socket.send(this.messageObjToString(eventName, dataObj))
} catch (err) {
this.emit('wsError', err)
}
}
disconnect() {
this.socket && this.socket.close(1000)
this.socket = null
}
getState() {
return this.socket && this.socket.readyState
}
getConnectResult() {
return this.connectResult
}
}
// const socket = MySocket.getObj(`${process.env.VUE_APP_WEBSOCKET_URL}`)
export default MySocket