websocket
在单个TCP连接上进行全双工通信的协议,可以实现服务端和客户端双向推送信息的协议。我们在使用webscoket通信时必须要注意的问题超时重连和心跳检测。
超时重连:当出现错误时客户端尝试重新连接websocket。
心跳检测:客户端长时间没接收到服务端消息,就向服务端发送请求,查看服务端是否还在,如果服务端在规定时间未回复消息则表明服务端由于某种原因中断了,那么客户端也就可以中断连接了。当然下面代码可以通过reconnect再次选择重连。
interface WsOptions {
url: string
reconnectCountLimit?: number
reconnectInterval?: number
heartDeadline?: number
watch?: boolean
}
class WebSocketClient {
#ws: WebSocket | undefined
#needReconnect: boolean
#count: number
url: string
reconnectCountLimit: number
watch: boolean
reconnectTime: NodeJS.Timeout | null
reconnectInterval: number
lastMessageTime: number
heartDeadline: number
heartCloseTime: NodeJS.Timeout | null
checkHeartTime: NodeJS.Timeout | null
receiveData: {
type: string
data: any
}[]
constructor(options: WsOptions) {
this.url = options.url
this.#needReconnect = false//是否需要重连
this.watch = options.watch || true//是否开启心跳
this.reconnectCountLimit = options.reconnectCountLimit || 3 //重连次数
this.#count = 0 //重连次数计数
this.reconnectTime = null//重连计时器
this.reconnectInterval = options.reconnectInterval || 5000//重连时间间隔
this.lastMessageTime = 0 //记录上次收到信息的时间 ->暂时没使用
this.heartDeadline = options.heartDeadline || 5000//心跳检测截止时间 每次收到信息后将在该时间后发送心跳消息,在该时间内未收到信息就终端ws连接
this.heartCloseTime = null //心跳关闭计时器
this.checkHeartTime = null //发送心跳消息计时器
this.#initWS()//启动ws
this.watch && this.heartBeat()//启动心跳检测
this.receiveData = []
}
get websocket() {
return this.#ws
}
//初始化websocket
#initWS() {
this.#ws = new WebSocket(this.url)
this.#ws.addEventListener('open', () => {
this.initState()
})
this.#ws.addEventListener('close', () => {
console.log('链接关闭')
})
this.#ws.addEventListener('error', () => {
this.#needReconnect = true
this.reconnect()
})
this.#ws.addEventListener('message', (e) => {
this.lastMessageTime = Date.now()
try {
const data = JSON.parse(e.data)
this.receiveData = this.receiveData.map(item => {
if (item.type === data.type) {
item.data = data.data
}
return item
})
} catch (e) {
console.log("无法解析")
}
this.closeCheckHeart()
this.watch && this.heartBeat()//启动心跳检测
})
}
//重连
reconnect() {
if (this.#needReconnect && this.#count < this.reconnectCountLimit) {
this.reconnectTime = setTimeout(() => {
this.#count++
this.#initWS()
}, this.reconnectInterval);
return
}
this.initState()
}
//初始化数据
initState() {
this.#needReconnect = false
this.#count = 0
this.reconnectTime && clearInterval(this.reconnectTime)
}
closeCheckHeart() {
this.checkHeartTime = null
this.heartCloseTime = null
clearTimeout(this.checkHeartTime!)
clearTimeout(this.heartCloseTime!)
}
send(data: any) {
const websocketStatus: Record<number, () => void> = {
0: () => {
console.warn("ws连接中")
},
1: () => {
this.#ws!.send(data)
},
3: () => {
this.#initWS()
},
2: () => {
console.warn("ws连接正在关闭")
}
}
websocketStatus[this.#ws!.readyState]()
}
//心跳检测
heartBeat() {
this.checkHeartTime = setTimeout(() => {
this.send(JSON.stringify({
type: 'heart'
}))
console.log("check websocket status")
}, this.heartDeadline)
this.heartCloseTime = setTimeout(() => {
if (Date.now() - this.lastMessageTime > this.heartDeadline * 2) {
this.#ws!.close()
this.closeCheckHeart()
}
}, this.heartDeadline * 2)
}
}
export const ws = new WebSocketClient({url: import.meta.env.VITE_WS_URL})
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 8888 });
wss.on('connection', function connection(ws) {
ws.on('message', function message(data) {
const res = data.toString()
console.log(res)
if (res.startsWith('{') && JSON.parse(res)?.type === 'heart') {
// ws.send('{heart:exist}')
}
});
wss.clients.forEach(client => {
client && client.send('广播信息')
})
ws.send('I am service');
setTimeout(() => {
ws.send('xxx');
},10000)
});
wss.onopen = function (e) {
console.log('服务端链接');
}