websocket心跳的实现(包括全部代码)

原创 2017年08月12日 16:35:48

本文主要讲的是如果设计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 && !this.state.notificationSocket) { // 用户登录了并且没有连接过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不会为0if(ws.bufferedAmount === 0 && ws.readyState === 1) { 
          ws.send('h&b');
          ws.last_health_time = time;
        }
      }
    }
    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没收到信息,代表服务器出问题了,关闭连接。如果收到消息了,重置该定时器。
        if(ws.readyState === 1) { // 为1表示连接处于open状态
          ws.keepAliveTimer = setInterval(() => {
            ws.keepalive();
          }, 1000)
        }

      }
      ws.onerror = () => {
        console.error('onerror')
      }
      ws.onmessage = (msg) => {
      /* 这一注释部分是我的业务逻辑代码,大家可以忽略
        msg = JSON.parse(msg.data);
        let chatObj = JSON.parse(localStorage.getItem(CHATOBJECT)) || {};
        if(msg && msg.senderUserId && !chatObj[msg.senderUserId]) chatObj[msg.senderUserId] = [];
        if(msg.content !== 'h&b') {
          if(msg.chat === true) { // 聊天
            // chatObj[msg.senderUserId] = [<p key={new Date().getTime()}>{msg.content}</p>, ...chatObj[msg.senderUserId]]
            chatObj[msg.senderUserId].unshift(msg.content);
            WindowNotificationUtils.notice(msg.title, msg.content, () => {
              const { history } = this.props;
              history.replace({
                pathname: '/sendNotice',
                search: `?senderUserId=${msg.senderUserId}` // 为什么放在url,因为刷新页面数据不会掉
              });
            })
            localStorage.setItem(CHATOBJECT, JSON.stringify(chatObj));
            this.props.dispatch({
              type: UPDATE_CHAT
            })
          } else { // 通知
            WindowNotificationUtils.notice(msg.title, msg.content);
          }
        }
      */
        // 收到消息,重置定时器
        clearTimeout(ws.receiveMessageTimer); 
        ws.receiveMessageTimer = setTimeout(() => {
          ws.close();
        }, 30000); // 30s没收到信息,代表服务器出问题了,关闭连接。
      }
      ws.onclose = () => {
        clearTimeout(ws.receiveMessageTimer);
        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 = tempWs.onopen;
          ws.onmessage = tempWs.onmessage;
          ws.onerror = tempWs.onerror;
          ws.onclose = tempWs.onclose;
          ws.keepalive = tempWs.keepalive;
          ws.last_health_time = -1;
        }
      }
    }
  }
}

以上就是websocket心跳的全部实现。看到断开网络后然后再脸上网络websocket又连上了,那一刻心里很喜悦。如果有什么问题,欢迎大家和我交流。

邮箱: 1227620310@qq.com
版权声明:本文为博主原创文章,未经博主允许不得转载。

使用Websocket实现消息推送(心跳)

本来以为写完了,结果最近和一个同事在讨论心跳的事情,这里再做一个补充。先说我的结论: WebSocket协议已经设计了心跳,这个功能可以到达检测链接是否可用 心跳是用来检测链接是否可用的,不一定支持...
  • ttdevs
  • ttdevs
  • 2017年03月17日 14:02
  • 8523

WebSocket心跳机制和代码

本文主要讲的是如果设计websocket心跳已经需要考虑哪些问题。 前言 在使用websocket的过程中,有时候会遇到客户端网络关闭的情况,而这时候在服务端并没有触发onclose事件。这样会:...

websocket 心跳连接

websocket连接时,如果长时间没有进行数据的通讯就会自动断开连接。为了不让其断开就在要断开的时候自动发送数据进行通讯,就产生了心跳连接的效果。 具体的操作就是在客户端建立连接的时候开启发送心跳...

互联网推送服务原理:长连接+心跳机制(MQTT协议)

互联网推送消息的方式很常见,特别是移动互联网上,手机每天都能收到好多推送消息,经过研究发现,这些推送服务的原理都是维护一个长连接(要不不可能达到实时效果),但普通的socket连接对服务器的消耗太大了...

初探和实现websocket心跳重连

心跳重连缘由 在使用websocket过程中,可能会出现网络断开的情况,比如信不好,或者网络临时性关闭,这时候websocket的连接已经断开, 而浏览器不会执行websocket 的 onclo...

websocket心跳的实现(转载)

版权声明:本文为博主原创文章,未经博主允许不得转载。 目录(?)[+] 本文主要讲的是如果设计websocket心跳已经需要考虑哪些问题。 前言 在使用...
  • imLWY
  • imLWY
  • 2017年09月13日 21:08
  • 178

websocket 心跳包重连

摘要 websocket heartbeat 上次我们讲过了websocket断线重连的问题,那么久会有人提出疑问了,心跳包重连跟断线重连有什么区别呢?  其实这两个都是为了达到一个目的,那就是...

使用Websocket实现消息推送(心跳)

使用Websocket实现消息推送(心跳) 标签: websocket 2017-03-17 14:02 224人阅读 评论(0) 收藏 举报  分类: websocket(2)  ...

JAVA长连接demo(含心跳检测)

[java] view plain copy package houlei.csdn.keepalive;      import java.io.Serializa...

JAVA实现长连接(含心跳检测)Demo

实现原理:        长连接的维持,是要客户端程序,定时向服务端程序,发送一个维持连接包的。        如果,长时间未发送维持连接包,服务端程序将断开连接。 客户端:       ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:websocket心跳的实现(包括全部代码)
举报原因:
原因补充:

(最多只允许输入30个字)