背景
由于业务需要,项目需要全局实例化一个websocket组件供全局调用,在连接成功后向后端发送token进行鉴权,再向后端发送需要监听的数据,后端按照前端发送的这些参数回推变动的数据给前端。
调研and实现
调研了些插件,发现都不太符合业务需要,于是自己写了一个简单的。但是实际使用的时候发现会报如下的错:
nginx超时断开
原来是:使用了nginx 代理,在长时间没有连接的情况下会断开,这样前端就收不到后端的推送。
解决办法
一、增加nginx 的超时时间:设置 proxy_read_timeout 尽量大一点,按业务需求将这个时间尽量设置的长一点。
二、前端需要定时主动判断连接状态来确认连接是否断开,断开的话就重连,这样也能解决因其他意外情况导致的连接断开问题。
终极优化代码
思路是:在不修改后端代码(不需要后端推送pong之类的数据 )的前提下,通过定时判断连接状态来实现。
经过验证,如果nginx 超时,会执行onclose 方法进行重连,前端也能知道websocket状态是断开的。
终极版代码如下:
import { ref } from 'vue'
const WebSocketPlugin = {
install(app) {
let ws = ref(null)
let message = ref(null)
let callback = ref(null)
const wsUrl = ref(null)
//避免ws重复连接
const lockReconnect = ref(false)
const connect = url => {
wsUrl.value = url
try {
if ('WebSocket' in window) {
ws.value = new WebSocket(url)
ws.value.onopen = () => {
//心跳检测重置
heartCheck.reset().start()
ws.value.send(
JSON.stringify({
token: localStorage.getItem('token')
})
)
sendAndMessage()
}
ws.value.onmessage = event => {
//拿到任何消息都说明当前连接是正常的
heartCheck.reset().start()
callback.value(JSON.parse(event.data))
}
ws.value.onclose = () => {
ws.value === null
reconnect(url)
}
ws.value.onerror = () => {
ws.value = null
reconnect(url)
}
}
} catch (e) {
reconnect(url)
}
}
const reconnect = url => {
if (lockReconnect.value) {
return
}
lockReconnect.value = true
setTimeout(function () {
connect(url)
lockReconnect.value = false
}, 2000)
}
// 关闭
const close = () => {
if (ws.value && ws.value.readyState !== WebSocket.CLOSED) {
ws.value = null
message.value = null
callback.value = null
}
}
//发送消息初始化
const sendAndMessage = ()=> {
if (ws.value?.readyState === WebSocket.OPEN && message.value) {
ws.value.send(
JSON.stringify({
tables: message.value
})
)
} else if (ws.value?.readyState !== WebSocket.CONNECTING) {
reconnect(wsUrl.value)
}
}
//页面初始化方法
const init = (msg, cb) => {
message.value = msg
callback.value = cb
sendAndMessage()
}
//心跳检测
const heartCheck = {
//这里设置10分钟发一次心跳
timeout: 10 * 60 * 1000,
timeoutObj: null,
reset: function () {
clearTimeout(this.timeoutObj)
return this
},
start: function () {
this.timeoutObj && clearTimeout(this.timeoutObj)
this.timeoutObj = setTimeout(function () {
//检测状态
sendAndMessage()
}, this.timeout)
}
}
app.provide('wsInit', init)
app.provide('wsClose', close)
app.provide('wsConnect', connect)
}
}
export default WebSocketPlugin
在main.js引入该插件:
import WebSocketPlugin from './plugins/websocket.js'
app.use(WebSocketPlugin)
在首页调用连接和关闭方法:
const wsConnect = inject('wsConnect')
const wsClose = inject('wsClose')
wsConnect(window.wsUrl + '/v1/system/watch')
//退出登录关闭连接
onUnmounted(() => {
wsClose()
})
在具体详情页发送监听数据:
const wsInit = inject('wsInit')
wsInit(['XXX'], () => {
//收到推送后执行的回调函数
})
网友评论