CHANSHIYU
GITHUBZERO
  • README
  • 時雨
    • 2017
      • 01 网站动态标题的两种方式
      • 02 RN App 外部唤醒踩坑记
    • 2018
      • 01 不一样の烟火
      • 02 Python 之禅
      • 03 Python 文件操作
    • 2019
      • 01 Aurora 食用指南
      • 02 Godaddy 域名找回记事
      • 03 一个接口的诞生
      • 04 SpringMVC 前后端传参协调
      • 05 主题集成友链访问统计
      • 06 Github Style 博客主题
      • 07 字符编码の小常识
      • 08 WSL 安装 Docker 实录
      • 09 Eriri comic reader
      • 10 Aurora 2.0
      • 11 jsDelivr 全站托管
      • 12 两年工作台变迁史
      • 13 春物
      • 14 一种优雅の笔记方式
    • 2020
      • 01 Telegram 电报机器人
      • 02 她的眼里有星辰
      • 03 文心雕龙
      • 04 软萌木子の有趣笔谈
      • 05 Telegram RSS 订阅频道
      • 06 水月雨银色飞船
      • 07 五年前旧照
    • 2021
      • 01 春宵苦短 2020
      • 02 风花
    • 2022
      • 01 小城新貌
      • 02 原神满级纪念
    • 2023
      • 01 2022 逆旅
      • 02 半透明背景图实现
      • 03 新年攒台海景房
  • 前端
    • JavaScript
      • 01 JavaScript 秘密花园
      • 02 JavaScript 正则技巧
      • 03 从浏览器解析 JS 运行机制
      • 04 Canvas 基础用法
      • 05 Blob Url And Data Url
      • 06 函数节流与函数防抖
      • 07 排序算法初探
      • 08 洗牌算法实现数组乱序
      • 09 正则匹配 match 和 exec
      • 10 正则匹配汉字
      • 11 JSX.Element vs ReactElement
      • 12 可选链与空值合并
      • 13 TypeScript 编码规范
      • 14 Typescript 中 interface 和 type 区别
      • 15 TypeScript 高级类型
      • 16 TypeScript 关键字
      • 17 TypeScript 映射类型
    • CSS
      • 01 Flex 弹性布局
      • 02 Position 定位
      • 03 CSS 逻辑属性
    • Node
      • 01 Node Tips
      • 02 七天学会 NodeJS
    • Note
      • 01 Note
      • 02 Code
      • 03 Snippets
      • 04 Git
    • React
      • 01 React Props Children 传值
      • 02 Use a Render Prop!
      • 03 React Hook
      • 04 React Hook 定时器
      • 05 Fetch data with React Hooks
      • 06 React 和 Vue 中 key 的作用
      • 07 useCallback 的正确使用方式
      • 08 useLayoutEffect 和 useEffect 的区别
      • 09 forwardRef 逃生舱
      • 10 React 条件渲染
    • Vue
      • 01 Vue Tips
      • 02 Vue 构建项目写入配置文件
      • 03 Vue 项目引入 SVG 图标
      • 04 Vue 一键导出 PDF
      • 05 动态可响应对象
      • 06 Vue 引入 SCSS
      • 07 Vue 路由权限控制
    • 实战系列
      • 01 WebSocket 心跳重连机制
      • 02 图片加解密二三事
      • 03 优雅实现 BackTop
      • 04 动态加载 JS 文件
      • 05 常用 DOM 方法比较
      • 06 AbortController 中断 fetch
      • 07 计算字符所占字节数
      • 08 Axios 自定义返回值类型
  • 后端
    • Java
      • 01 面向对象基本特征与原则
      • 02 Java 数据类型
      • 03 Java String
      • 04 Java 只有值传递
      • 05 Java final 与 static
      • 06 Java Object 通用方法
      • 07 Java 继承
      • 08 Java 反射
      • 09 Java 异常
      • 10 Java 容器
      • 11 Java 虚拟机
      • 12 Java IO
      • 13 Java HashMap
      • 14 Java List
      • 15 Java Stream
      • 16 Java 枚举
      • 17 Java 日期与时间
      • 18 Java fail fast
      • 19 Java BiFunction 和 BinaryOperator
    • 并发编程
      • 01 Java 并发
      • 02 synchronized
      • 03 volatile
      • 04 ReentrantLock
      • 05 ReadWriteLock
      • 06 StampedLock
      • 07 CompletableFuture
      • 08 ForkJoin
      • 09 ThreadLocal
      • 10 CountDownLatch
      • 11 ThreadPoolExecutor
      • 12 ExecutorService
      • 13 Atom 原子类
      • 14 BlockingQueue
    • 高效编程
      • 01 30 seconds of java8
      • 02 函数式替代 for 循环
      • 03 Java 字符串拼接
      • 04 单例模式的几种实现
      • 05 HashMap 排序
    • 理论概念
      • 01 Java Servlet
      • 02 Java 服务端分层模型
      • 03 经典排序算法
      • 04 LRU 缓存淘汰算法
      • 05 BloomFilter 判断元素存在
      • 06 Java HashMap 面试大全
      • 07 HTTP 状态码详解
      • 08 Cookie 和 Session
      • 09 基于消息队列的分布式事务解决方案
      • 10 微服务之所见
    • 实战系列
      • 01 AES CBC 加解密
      • 02 Magic 魔数获取文件类型
      • 03 获取请求 IP 地址
      • 04 Kaptcha 与数学公式验证码
      • 05 Netty 获取客户端 IP.md
      • 06 高性能无锁队列 Disruptor.md
      • 07 前后端接入阿里云盾
    • Linux
      • 01 Linux 文件权限系统
      • 02 Linux 常用软件安装
      • 03 CentOS 防火墙
    • MySQL
      • 01 MySQL
      • 02 SQL 语句 where 1=1
      • 03 truncate 和 delete
      • 04 事务
      • 05 关系模型
      • 06 Mybatis
      • 07 MySQL 查看数据库表详情
    • Nginx
      • 01 Nginx 指北
      • 02 nginx gzip 压缩
    • Note
      • 01 Vagrant
      • 02 Docker
      • 03 Lombok
      • 04 Swagger
      • 05 Redis
    • Spring
      • 01 Spring Boot
      • 02 Spring Validation
      • 03 Spring Data
      • 04 Spring 容器
      • 05 Spring AOP
      • 06 Spring Transactional 注解
      • 07 Spring Cloud Netflix
      • 08 Spring Cloud Alibaba
      • 09 Spring Security oAuth2
      • 10 Spring Boot 跨域解决方式
      • 11 Spring Boot 请求拦截
      • 12 Spring Boot 异步编程
      • 13 Spring Boot 定时任务
      • 14 Spring Boot 管理 bean
      • 15 Mybatis 逆向代码生成
      • 16 JWT
      • 17 JPA
      • 18 Apache Shiro
      • 19 Spring 异步请求
  • 书斋
    • ES6 标准入门
      • 01 变量声明与解构赋值
      • 02 语法的扩展
      • 03 数据类型与数据结构
      • 04 Proxy 和 Reflect
      • 05 异步编程 Promise
      • 06 Iterator 和 for of 循环
      • 07 Generator 函数
      • 08 Async 函数
      • 09 Class 类
    • JavaScript 设计模式
      • 01 基础知识
      • 02 设计模式(上)
      • 03 设计模式(下)
      • 04 设计原则和编程技巧
  • 纸函
    • 01 Interview
    • 02 Ceph
    • 03 动态规划
    • 04 Document.designMode
    • 2023-01-10
  • 万藏
    • 文档
      • 01 Git 文档
      • 02 Linux 命令大全
      • 03 七天学会 NodeJS
      • 04 Algorithms
    • 工具
      • 01 Nginx Config
      • 02 ProcessOn
      • 03 Flat Icon
      • 04 Regexper
      • 05 TempMail
      • 06 Carbon
由 GitBook 提供支持
在本页
  • WebSocket
  • 心跳重连
  • 0x01 初始化
  • 0x02 发送心跳包与重连
  • 0x03 实例销毁
  • 封装 npm 包
  • 安装
  • 引入与使用
  • 属性
  • 发送消息
  • 断开连接

这有帮助吗?

  1. 前端
  2. 实战系列

01 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, chanshiyu!')

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 包

安装

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 ฅ●ω●ฅ

上一页实战系列下一页02 图片加解密二三事

最后更新于2年前

这有帮助吗?

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

websocket-heartbeat
websockethb