https://juejin.cn/post/6844904007593361415?searchId=202401100923491BA6C3FAA62F75EA1E17
https://www.jianshu.com/p/e4532bd0da57
前端作为主动方,定时给后端发送ping消息,解决1.网络问题引起的websocket断开(如果设备网络断开,不会触发任何函数)2.后端websocket服务也可能出现异常,连接断开后但是前端也并没有收到通知,(不会收到通知)
为什么需要心跳机制?
在使用原生 Websocket 的时候,如果设备网络断开,不会立刻触发 Websocket 的任何事件,前端也就无法得知当前连接是否已经断开。这个时候如果调用 Websocket.send 方法,浏览器才会发现链接断开了,便会立刻或者一定时间后(不同浏览器或者浏览器版本可能表现不同)触发 onclose 函数。
后端 Websocket 服务也可能出现异常,造成连接断开,这时前端也并没有收到断开通知,因此需要前端定时发送心跳消息 ping,后端收到 ping 类型的消息,立马返回消息,告知前端连接正常。如果一定时间没收到消息,就说明连接不正常,前端便会执行重连。
所以现在就有以下两个问题
-
- 如果前端没有发送消息,如果网络断开了,前端是不知道的,没法收到后端发过来的消息
-
- 网络断开后,后端
websocket服务出现异常,但是没办法通知到前端
- 网络断开后,后端
为了解决以上两个问题,以前端作为主动方,定时发送 ping 消息,用于检测网络和前后端连接问题。一旦发现异常,前端持续执行重连逻辑,直到重连成功。
// WebSocket构造函数,创建WebSocket对象
let ws = new WebSocket('ws://localhost:8888')
// 连接成功后的回调函数
ws.onopen = function (params) {
console.log('客户端连接成功')
// 向服务器发送消息
ws.send('hello')
};
// 从服务器接受到信息时的回调函数
ws.onmessage = function (e) {
console.log('收到服务器响应', e.data)
};
// 连接关闭后的回调函数
ws.onclose = function(evt) {
console.log("关闭客户端连接");
};
// 连接失败后的回调函数
ws.onerror = function (evt) {
console.log("连接失败了");
};
// 监听窗口关闭事件(关闭刷新),当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,这样服务端会抛异常。
类似方法:https://blog.csdn.net/weixin_44521565/article/details/110135974
// window.onbeforeunload = function() {
// ws.close();
// }
window.onbeforeunload = function() {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.close()
}
this.websocket = null; // 将WebSocket对象设置为null
}
主要看这段代码:
<template>
<div class="box">
websocket
</div>
</template>
<script>
const ModeCode = {
// websocket消息类型
MSG: 'message', // 普通消息
HEART_BEAT: 'heart_beat' // 心跳
};
const heartCheck = {
timeout: 30 * 1000, //执行心跳的时间间隔
timer: null, // 执行心跳的定时器
serverTimeout: 60 * 1000, //心跳返回数据的超时时间--------超时未返回数据,说明后端断开了 触发close实现reconnect重连
serverTimer: null, //服务器超时定时器
reset() {
this.timer && clearTimeout(this.timer)
this.serverTimer && clearTimeout(this.serverTimer)
},
start(ws) {
this.reset()
this.timer = setTimeout(() => {
// console.log('发送心跳,后端收到后,返回一个心跳消息')
// onmessage拿到返回的心跳就说明连接正常
ws.send(
JSON.stringify({
ModeCode: ModeCode.HEART_BEAT, //心跳类型
msg: new Date()
})
)
// 首先上面发送了心跳,这里在等待返回 如果有返回 则会在onmessage里重新调用start发送心跳,会重置定时器,超时(serverTimeout)没返回数据 就会close调用重连
this.serverTimer = setTimeout(() => {
// 如果超过一定时间还没响应(响应后触发重置),说明后端断开了
ws.close()
}, this.serverTimeout)
}, this.timeout)
}
}
export default {
name: 'Websocket',
data() {
return {
asrc,
wsuri: 'ws://123.207.167.163:9010/ajaxchattest', // ws wss
lockReconnect: false, // //避免重复连接
maxReconnect: 5, // 最大重连次数,若连接失败
socket: null
}
},
mounted() {
this.initWebSocket()
},
methods: {
reconnect() {
console.log('尝试重连')
if (this.lockReconnect || this.maxReconnect <= 0) {
return
}
this.lockReconnect = true
setTimeout(() => {
// this.maxReconnect-- // 不做限制 连不上一直重连
this.initWebSocket()
this.lockReconnect = false
}, 60 * 1000)
},
initWebSocket() {
try {
if ('WebSocket' in window) {
this.socket = new WebSocket(this.wsuri)
} else {
console.log('您的浏览器不支持websocket')
}
this.socket.onopen = this.websocketonopen
this.socket.onerror = this.websocketonerror
this.socket.onmessage = this.websocketonmessage
this.socket.onclose = this.websocketclose
} catch (e) {
this.reconnect()
}
},
websocketonopen() {
console.log('WebSocket连接成功', this.socket.readyState)
// 发送心跳
heartCheck.start(this.socket)
// 发送页面数据
this.websocketsend()
},
websocketonerror(e) {
console.log('WebSocket连接发生错误', e)
this.reconnect()
},
websocketonmessage(e) {
//不管拿到心跳包还是正常返回的数据 拿到任何消息都说明当前连接是正常的 处理数据时候 可以进行判断
// 消息获取成功,重置心跳
heartCheck.start(this.socket)
switch (data.ModeCode) {
case "message":
console.log("收到消息" + data.msg)
break
case "heart_beat":
console.log(`收到心跳${data.msg}`)
break
}
},
websocketclose(e) {
console.log('connection closed (' + e.code + ')')
this.reconnect()
},
websocketsend() {
let data = {
id: 'a1b2c3'
}
this.socket.send(JSON.stringify({
ModeCode: ModeCode.MSG, //消息类型
msg: data
}))
}
},
destroyed() {
this.socket.close()
}
}
</script>
<style lang="scss" scoped></style>
心跳机制 预防客户端网络断开时候只有在send发送数据时候才会触发close 和服务端异常时候客户端不能感知到 这时候也只有send发送后超时未接受到返回的信息时 触发close 两者都需要发送send信息 才能触发close 所以定时发送心跳
发送心跳是在初始ws连接成功后 直接发送心跳 然后在接收到也就是onmessage时候重置心跳(上面的start方法) 如果心跳发送出去一直没有返回信息 当超过心跳超时时间时候 就会触发close方法 启动重连
重连里(reconnect方法)可以设置重连条件限制 比如重连次数 超过次数直接return 或者有的条件不想重连了 可以加个开关(lockReconnect) 重连实际也就是再次执行initWebsocket
启动start重连 会定时(settimeout几秒后)发送send心跳包 如果超时settimeout几秒后还没收到就会触发close 但是要是在超时时间内收到了(onmessage里接受到了) 就会重新执行start 重新执行时候也就顺带把上一次的定时器清空了(一个是发送心跳定时器 一个是返回心跳信息的超时定时器)
class封装
* @desc websocket 类
* @param {String} path 连接地址
* @param {Function} onmessage 连接地址
* @return
* @author ppc
* @date 2022-08-29 11:20:58
*/
import {
Message
} from 'element-ui'
export class dsWebSocket {
constructor(params) {
this.params = params
this.wesock = null // 建立的连接
this.lockReconnect = false // 是否真正的建立连接
this.timeout = 10 * 1000, // n秒一次的心跳
this.timeoutObj = null // 心跳倒计时
this.serverTimeoutObj = null // 心跳倒计时
this.timeoutnum = null // 断开 重连倒计时
console.log(this,'this constructor')
if ('WebSocket' in window) {
this.init(params)
} else {
Message({
message: "浏览器不支持,请换浏览器再试",
type: "error"
});
}
}
/**
* @desc 初始化
* @param
* @return
* @author ppc
* @date 2022-08-29 11:21:21
*/
init(params) {
//建立连接
this.websock = new WebSocket(params.path)
// 连接成功
this.websock.onopen = this.onOpen.bind(this)
//连接错误
this.websock.onerror = this.onError.bind(this)
//接收信息
this.websock.onmessage = this.getInformation.bind(this)
//连接关闭
this.websock.onclose = function () {}
this.websock.onclose = this.close.bind(this)
}
/**
* @desc 重新连接
* @param
* @return
* @author ppc
* @date 2022-08-29 11:37:36
*/
reconnect() {
if (this.lockReconnect) {
return
}
this.lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多
this.timeoutnum && clearTimeout(this.timeoutnum);
this.timeoutnum = setTimeout(function () {
//新连接
this.init();
this.lockReconnect = false;
}, 5000);
}
/**
* @desc 重置心跳
* @param
* @return
* @author ppc
* @date 2022-08-29 11:38:05
*/
reset() {
console.log('this',this)
//清除时间
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
//重启心跳
console.log('-----重启心跳------')
this.start();
}
/**
* @desc 开启心跳
* @param
* @return
* @author ppc
* @date 2022-08-29 11:38:30
*/
start() {
this.timeoutObj && clearTimeout(this.timeoutObj);
this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
this.timeoutObj = setTimeout( () =>{
//这里发送一个心跳,后端收到后,返回一个心跳消息,
if (this.websock&&this.websock.readyState == 1) {
// console.log(222222)
//如果连接正常
this.websock.send('{"wsRequestType":"HEARTBEAT"}');
// console.log("发送消息");
} else {
//否则重连
// console.log("重新链接")
// this.reconnect();
}
this.serverTimeoutObj = setTimeout(function () {
//超时关闭
this.websock.close();
}, this.timeout);
}, this.timeout);
}
/**
* @desc 连接成功
* @param
* @return
* @author ppc
* @date 2022-08-29 11:39:32
*/
onOpen() {
//提示成功
// this.isOnline = 1;
//开启心跳
console.log(this,'this')
console.log('-----连接成功------')
this.start();
}
/**
* @desc 连接失败
* @param
* @return
* @author ppc
* @date 2022-08-29 11:41:06
*/
onError() {
// console.log("WebSocket连接发生错误");
// this.isOnline = 2;
//重连
this.reconnect();
}
/**
* @desc 连接关闭事件
* @param
* @return
* @author ppc
* @date 2022-08-29 11:41:27
*/
close() {
// this.reconnect();
}
/**
* @desc 接收服务器推送的信息
* @param
* @return
* @author ppc
* @date 2022-08-29 11:41:59
*/
getInformation(event) {
//收到服务器信息,心跳重置
this.reset();
const redata = JSON.parse(event.data);
this.params.onmessage(redata)
return redata
}
/**
* @desc 向服务器发送信息
* @param
* @return
* @author ppc
* @date 2022-08-29 11:43:33
*/
send() {
}
}
var that = this;
const webSocket = new dsWebSocket({
path: wsuri,
onmessage: function (msg) {
console.log(msg, "返回的数据);
//数据处理
that.websocketonmessage(msg);
},
});
在onopen时候发送心跳,在onmessage时候晴空心跳定时器和心跳超时定时器,但是如果心跳超时会直接调用close传递自定义code(相当于非正常关闭 !1000),然后在onclose事件里面判断假如非正常关闭,启动重连(可以设置重连最大次数)正常手动close时候需要socket设为null,自定义状态为Disconnected