前端 实战项目·WebSocket 心跳重连机制

作者 : jamin 本文共4375个字,预计阅读时间需要11分钟 发布时间: 2020-10-18 共993人阅读

WebSocket 心跳重连机制

WebSocket 是一种网络通信协议,它使得客户端和服务器之间的数据交换变得更加简单。最近在项目中使用 WebSocket 实现了一个简单在线聊天室功能,在此探究下心跳重连的机制。

WebSocket

WebSocket 允许服务端主动向客户端推送数据。之前很多网站为了实现推送技术,采用的技术都是轮询,不仅效率低,也浪费了很多带宽等资源。HTML5 定义了 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

WebSocket 的优势:

  • 较少的控制开销
  • 更强的实时性
  • 保持连接状态
  • 更好的二进制支持
  • 可以支持扩展
  • 更好的压缩效果

WebSocket 最大的优势就是能够保持前后端消息的长连接,但是在某些情况下,长连接失效并不会得到及时的反馈,前端并不知道连接已断开。例如用户网络断开,并不会触发 websocket 的任何事件函数,这个时候如果发送消息,消息便无法发送出去,浏览器会立刻或者一定短时间后(不同浏览器或者浏览器版本可能表现不同)触发 onclose 函数。

为了避免这种情况,保证连接的稳定性,前端需要进行一定的优化处理,一般采用的方案就是心跳重连。前后端约定,前端按一定间隔发送一个心跳包,后端接收到心跳包后返回一个响应包,告知前端连接正常。如果一定时间内未接收到消息,则认为连接断开,前端进行重连。

心跳重连

通过以上分析,可以得到实现心跳重连的关键是按时发送心跳消息和检测响应消息并判断是否进行重连,所以首先设置 4 个小目标:

  • 可以按一定间隔发送心跳包
  • 连接错误或者关闭时能够自动重连
  • 若在一定时间间隔内未接收消息,则视为断连,自动进行重连
  • 可以自定义心跳消息并设置最大重连次数

0x01 初始化

为了方便复用,这里决定将 WebSocket 管理封装为一个工具类 WebsocketHB,通过传入配置对象来自定义心跳重连机制。

class WebsocketHB {
  constructor({
    url, // 连接客户端地址
    pingTimeout = 8000, // 发送心跳包间隔,默认 8000 毫秒
    pongTimeout = 15000, // 最长未接收消息的间隔,默认 15000 毫秒
    reconnectTimeout = 4000, // 每次重连间隔
    reconnectLimit = 15, // 最大重连次数
    pingMsg // 心跳包的消息内容
  }) {
    // 初始化配置
    this.url = url
    this.pingTimeout = pingTimeout
    this.pongTimeout = pongTimeout
    this.reconnectTimeout = reconnectTimeout
    this.reconnectLimit = reconnectLimit
    this.pingMsg = pingMsg

    this.ws = null
    this.createWebSocket()
  }

  // 创建 WS
  createWebSocket() {
    try {
      this.ws = new WebSocket(this.url)
      this.ws.onclose = () => {
        this.onclose()
      }
      this.ws.onerror = () => {
        this.onerror()
      }
      this.ws.onopen = () => {
        this.onopen()
      }
      this.ws.onmessage = event => {
        this.onmessage(event)
      }
    } catch (error) {
      console.error('websocket 连接失败!', error)
      throw error
    }
  }

  // 发送消息
  send(msg) {
    this.ws.send(msg)
  }
}

食用方式:

const ws = new WebsocketHB({
  url: 'ws://xxx'
})

ws.onopen = () => {
  console.log('connect success')
}
ws.onmessage = e => {
  console.log(`onmessage: ${e.data}`)
}
ws.onerror = () => {
  console.log('connect onerror')
}
ws.onclose = () => {
  console.log('connect onclose')
}
ws.send('Hello, nicestar!')

0x02 发送心跳包与重连

这里使用 setTimeout 模拟 setInterval 定时发送心跳包,避免定时器队列阻塞,并且限制最大重连次数。需要注意的是每次进行重连时加锁,避免进行无效重连,同时在每次接收消息时,清除最长间隔消息重连定时器,能接收消息说明连接正常,不需要重连。

class WebsocketHB {
  constructor() {
    // ...
    // 实例变量
    this.ws = null
    this.pingTimer = null // 心跳包定时器
    this.pongTimer = null // 接收消息定时器
    this.reconnectTimer = null // 重连定时器
    this.reconnectCount = 0 // 当前的重连次数
    this.lockReconnect = false // 锁定重连
    this.lockReconnectTask = false // 锁定重连任务队列

    this.createWebSocket()
  }

  createWebSocket() {
    // ...
    this.ws = new WebSocket(this.url)
    this.ws.onclose = () => {
      this.onclose()
      this.readyReconnect()
    }
    this.ws.onerror = () => {
      this.onerror()
      this.readyReconnect()
    }
    this.ws.onopen = () => {
      this.onopen()

      this.clearAllTimer()
      this.heartBeat()
      this.reconnectCount = 0
      // 解锁,可以重连
      this.lockReconnect = false
    }
    this.ws.onmessage = event => {
      this.onmessage(event)

      // 超时定时器
      clearTimeout(this.pongTimer)
      this.pongTimer = setTimeout(() => {
        this.readyReconnect()
      }, this.pongTimeout)
    }
  }

  // 发送心跳包
  heartBeat() {
    this.pingTimer = setTimeout(() => {
      this.send(this.pingMsg)
      this.heartBeat()
    }, this.pingTimeout)
  }

  // 准备重连
  readyReconnect() {
    // 避免循环重连,当一个重连任务进行时,不进行重连
    if (this.lockReconnectTask) return
    this.lockReconnectTask = true
    this.clearAllTimer()
    this.reconnect()
  }

  // 重连
  reconnect() {
    if (this.lockReconnect) return
    if (this.reconnectCount > this.reconnectLimit) return

    // 加锁,禁止重连
    this.lockReconnect = true
    this.reconnectCount += 1
    this.createWebSocket()
    this.reconnectTimer = setTimeout(() => {
      // 解锁,可以重连
      this.lockReconnect = false
      this.reconnect()
    }, this.reconnectTimeout)
  }}

  // 清空所有定时器
  clearAllTimer() {
    clearTimeout(this.pingTimer)
    clearTimeout(this.pongTimer)
    clearTimeout(this.reconnectTimer)
  }
}

0x03 实例销毁

最后给工具类加一个销毁方法,在实例销毁的时候设置一个禁止重连锁,避免销毁的时候还在尝试重连,并且清空所有定时器,关闭长连接。

class WebsocketHB {
  // 重连
  reconnect() {
    if (this.forbidReconnect) return
    //...
  }

  // 销毁 ws
  destroyed() {
    // 如果手动关闭连接,不再重连
    this.forbidReconnect = true
    this.clearAllTimer()
    this.ws && this.ws.close()
  }
}

封装 npm 包

到这里,WebSocket 工具类心跳重连功能基本封装完成,可以尝试开始食用。这里将最终完成代码上传到 Github,并将其封装上传到 npm 以便今后在项目中使用, 有兴趣可以尝试一下 websockethb

安装

npm install websockethb

引入与使用

import WebsocketHB from 'websockethb'

const ws = new WebsocketHB({
  url: 'ws://xxx'
})

ws.onopen = () => {
  console.log('connect success')
}
ws.onmessage = e => {
  console.log(`onmessage: ${e.data}`)
}
ws.onerror = () => {
  console.log('connect onerror')
}
ws.onclose = () => {
  console.log('connect onclose')
}

属性

属性 必填 类型 默认值 描述
url true string none websocket 服务端接口地址
pingTimeout false number 8000 心跳包发送间隔
pongTimeout false number 15000 15 秒内没收到后端消息便会认为连接断开
reconnectTimeout false number 4000 尝试重连的间隔时间
reconnectLimit false number 15 重连尝试次数
pingMsg false string "heartbeat" 心跳包消息
const opts = {
  url: 'ws://xxx',
  pingTimeout: 8000, // 发送心跳包间隔,默认 8000 毫秒
  pongTimeout: 15000, // 最长未接收消息的间隔,默认 15000 毫秒
  reconnectTimeout: 4000, // 每次重连间隔
  reconnectLimit: 15, // 最大重连次数
  pingMsg: 'heartbeat' // 心跳包的消息内容
}
const ws = new WebsocketHB(opts)

发送消息

ws.send('Hello World')

断开连接

ws.destroyed()

Just enjoy it ฅ●ω●ฅ

本站所提供的部分资源来自于网络,版权争议与本站无关,版权归原创者所有!仅限用于学习和研究目的,不得将上述内容资源用于商业或者非法用途,否则,一切后果请用户自负。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容资源。如果上述内容资对您的版权或者利益造成损害,请提供相应的资质证明,我们将于3个工作日内予以删除。本站不保证所提供下载的资源的准确性、安全性和完整性,源码仅供下载学习之用!如用于商业或者非法用途,与本站无关,一切后果请用户自负!本站也不承担用户因使用这些下载资源对自己和他人造成任何形式的损失或伤害。如有侵权、不妥之处,请联系站长以便删除!
金点网络-全网资源,一网打尽 » 前端 实战项目·WebSocket 心跳重连机制

常见问题FAQ

免费下载或者VIP会员专享资源能否直接商用?
本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。
是否提供免费更新服务?
持续更新,永久免费
是否经过安全检测?
安全无毒,放心食用

提供最优质的资源集合

立即加入 友好社区
×