WebSocket ⼼跳机制和代码
本⽂主要讲的是如果设计websocket⼼跳已经需要考虑哪些问题。
前⾔在使⽤websocket的过程中,有时候会遇到客户端⽹络关闭的情况,⽽这时候在服务端并没有触发onclose事件。这样会:多余的连接
服务端会继续给客户端发数据,这些数据会丢失
所以就需要⼀种机制来检测客户端和服务端是否处于正常连接的状态。这就是websocket⼼跳,这个名字⾮常⽣动形象,还有⼼跳说明还活着(保持正常连接),没有⼼跳说明已经挂掉了(连接断开了)。
要解决的问题
我的代码主要解决了以下⼏个问题。
1. 连接上之后,每秒发送⼀个⼼跳,服务器同样返回⼀个⼼跳,⽤来表⽰服务器没挂。
2. 断线重连(我们测试的环境是断开⽹络连接),断开⽹络后,⼼跳包⽆法发送出去,所以如果当前时间距离上次成功⼼跳的时间超过20秒,说明连接已经出现问题了,此时需要关闭连接。
3. 第⼀次关闭连接时websocket会尝试重连,设置了⼀个时间期限,10秒。10秒内如果能连上(恢复⽹络连接)就可以继续收发消息,连不上就关闭了,并且不会重连。
4. 30秒内收不到服务器消息(⼼跳每秒发送),我就认为服务器已经挂了,就会调⽤close事件,然后进⼊第3步。需要什么开始考虑得不周到,命名不规范。
⼀个定时器ws.keepAliveTimer ,⽤来每秒发送⼀次⼼跳。
上次⼼跳成功的时间ws.last_health_time 以及当前时间let time = new Date().getTime();。
断开连接(ws.close())时的时间reconnect ,因为在close事件发⽣后需要重连10秒。
是否已经重连过reconnectMark 。
断开连接(ws.close())时需要保存ws对象tempWs 。我曾试图ws = { ...ws }发现会丢失绑定的事件。
⼀个定时时间为30秒的setTimeout定时器ws.receiveMessageTimer ,⽤来表⽰服务器是否在30秒内返回了消息。代码部分
我是在react中使⽤websocket⼼跳的。当⽤户登录时我会建⽴websocket连接。由于使⽤了redux,所以
该部分代码放在componentWillReceiveProps 中。componentWillReceiveProps(nextProps) { if(nextProps.isLogin && !ificationSocket) { // ⽤户登录了并且没有连接过websocket let ws = new WebSocket(`${chatUrl}/${nextProps.userId}`); ws.last_health_time = -1; // 上⼀次⼼跳时间 ws.keepalive = function() { let time = new Date().getTime(); if(ws.last_health_time !== -1 && time - ws.last_health_time > 20000) { // 不是刚开始连接并且20s ws.close() } else { // 如果断⽹了,ws.send 会⽆法发送消息出去。ws.bufferedAmount 不会为0。 if(ws.bufferedAmount === 0 && ws.readyState === 1) { ws.send('h&b'); ws.last_health_time = time; } } } if(ws) {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if(ws) { let reconnect = 0; //重连的时间 let reconnectMark = false; //是否重连过 this.setState({ notificationSocket: true }) ws.onopen = () => { reconnect = 0; reconnectMark = false; ws.receiveMessageTimer = setTimeout(() => { ws.close(); }, 30000); // 30s 没收到信息,代表服务器出问题了,关闭连接。如果收到消息了,重置该定时器。 adyState === 1) { // 为1表⽰连接处于open 状态 ws.keepAliveTimer = setInterval(() => { ws.keepalive(); }, 1000) } } ws.onerror = () => { ('onerror') } ws.onmessage = (msg) => { /* 这⼀注释部分是我的业务逻辑代码,⼤家可以忽略 msg = JSON.parse(msg.data); let chatObj = JSON.Item(CHATOBJECT)) || {}; if(msg && msg.senderUserId && !chatObj[msg.senderUserId]) chatObj[msg.senderUserId] = []; t !== 'h&b') { if(msg.chat === true) { // 聊天 // chatObj[msg.senderUserId] = [<p key={new Date().getTime()}>{t}</p>, ...chatObj[msg.senderUserId]] chatObj[msg.senderUserId].t); ice(msg.title, t, () => { const { history } = this.props; place({ pathname: '/sendNotice', search: `?senderUserId=${msg.senderUserId}` // 为什么放在url ,因为刷新页⾯数据不会掉 }); }) localStorage.setItem(CHATOBJECT, JSON.stringify(chatObj))
; this.props.dispatch({ type: UPDATE_CHAT }) } else { // 通知 ice(msg.title, t); } } */ // 收到消息,重置定时器 iveMessageTimer); ws.receiveMessageTimer = setTimeout(() => { ws.close(); }, 30000); // 30s 没收到信息,代表服务器出问题了,关闭连接。 } ws.onclose = () => { iveMessageTimer); clearInterval(ws.keepAliveTimer); if(!reconnectMark) { // 如果没有重连过,进⾏重连。 reconnect = new Date().getTime(); reconnectMark = true; } let tempWs = ws; // 保存ws 对象 if(new Date().getTime() - reconnect >= 10000) { // 10秒中重连,连不上就不连了 ws.close(); } else { ws = new WebSocket(`${chatUrl}/${nextProps.userId}`); ws.onopen = pen;
181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
websocket和socket} 83848586878889909192
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论