当前位置: 首页 > news >正文

回顾websocket心跳机制以及断线重连(服务端为node)

文章目录

  • 前言
  • 心跳机制触发过程
    • 核心原理
    • 思路&实现
  • 断线重连触发过程
  • 完整代码
    • server.js
    • index.html

前言

之前一直对websocket 的心跳机制和断线重连有个模糊的了解,项目中也不是我负责写这块的,最近有点时间,但是看网上的文章 总感觉过于冗长且直接贴代码,阅读感受不太好,因此自己总结一篇

心跳机制触发过程

核心原理

心跳机制通过定时发送 “无业务意义的探测包” 来实现的
若发送方在规定时间内未收到响应,则判断链接失效,触发重连
实际上就是客户端和服务端有来有回的检测,若服务端不回应,则判定异常并重连

思路&实现

需要的变量
心跳发送计时器 heartbeatTimer
心跳超时检测计时器 heartbeatTimeoutTimer;
发送心跳间隔:const HEARTBEAT_INTERVAL = 5000;
超时阈值:const HEARTBEAT_TIMEOUT = 10000;
需要的函数
开启心跳的函数:startHeartbeat()
停止心跳:stopHeartbeat()
心跳超时检测函数:startHeartbeatTimeout()

流程

  • 在连接成功后启动心跳检测
  • 每隔3S发送心跳消息
  • 如果服务端超过阈值时间,则判定异常,触发重连

核心代码

  • startHeartbeat()
    本质上就是开启一个计时器而已,每n秒发送一次无交互意义的消息,并启动检测(10s 倒计时)
 //  心跳机制function startHeartbeat() {stopHeartbeat(); // 先清理之前的心跳定时器heartbeatInterval = setInterval(function () {if (ws && ws.readyState === WebSocket.OPEN) {// 发送心跳消息ws.send('Heartbeat 心跳检测');}}, HEARTBEAT_INTERVAL); // 每5秒发送一次心跳// 启动检测startHeartbeatTimeout()}
  • startHeartbeatTimeout
    当开启心跳的时候进行超时检测,如果后端响应心跳消息了那么重新进行倒计时,未响应则关闭
    // 心跳超时检测function startHeartbeatTimeout() {console.log("心跳超时检测开始");// 重新计算clearTimeout(heartbeatTimeoutTimer);heartbeatTimeoutTimer = setTimeout(() => {// 超时未收到响应,主动断开并重连console.error('心跳超时,连接异常');ws.close(1000, 'header timeout'); // 主动关闭}, HEARTBEAT_TIMEOUT);}
  • stopHeartBeat
    function stopHeartbeat() {if (heartbeatInterval) {clearInterval(heartbeatInterval);heartbeatInterval = null;}}
  1. 在连接上websocket 后进行心跳
    ws.onopen = function (e) {console.log('ws 已经连接', e);ws.send('Hello server')// 开始心跳startHeartbeat()}
  1. 在接收到心跳消息后进行倒计时检测
   ws.onmessage = function (e) {const msg = e.data.trim();console.log("接收到的消息", msg);// 关键:仅当消息是「心跳响应」时,才重置超时计时器if (msg === "Heartbeat_ack") {startHeartbeatTimeout()}// 其他消息(如服务端的"hello client!")不处理心跳超时,避免误重置}

断线重连触发过程

断线重连 实际上就是当非正常关闭时进行重连逻辑
需要的变量
是否主动关闭:isManualClose
需要的函数
重连函数:startReconnect()
监听网络:window.addEventListener('online/offline')

    // 启动重连function startReconnect() {// 清除之前的重连定时器(因为可能之前重连过)if (reconnectTimeout) {clearTimeout(reconnectTimeout);}// 检查网络状态,仅在线时重连if (!navigator.onLine) {console.log('网络离线,等待恢复...');return;}reconnectTimeout = setTimeout(() => {console.log('尝试重连...');connect(); // 重建连接}, RECONNECT_INTERVAL);}
    // // 监听网络状态变化window.addEventListener('online', function () {console.log('网络已连接');// 网络恢复时尝试重连if (!ws || ws.readyState === WebSocket.CLOSED || ws.readyState === WebSocket.CLOSING) {console.log('网络恢复,尝试重新连接...');isManualClose = false; // 重置手动关闭标志startReconnect();}});window.addEventListener('offline', function () {console.log('网络已断开');// 网络断开时的处理if (ws) {console.log('网络断开,WebSocket连接可能已失效');ws.close()}// 清理重连定时器if (reconnectTimeout) {clearTimeout(reconnectTimeout);}});

完整代码

注意:server.js 中我只发送了4次心跳响应,是为了测试心跳逻辑是否正确
如果要测试断线重连,可以在谷歌开发者工具中的network 模块进行网络离线,不需要手动关闭wifi

server.js

// 引入需要的模块
const WebSocket = require("ws");
const express = require("express");// 创建 Express 应用
const app = express();// 设置静态文件服务
app.use(express.static("public"));// 添加 CORS 头
app.use((req, res, next) => {res.setHeader("Access-Control-Allow-Origin", "*");next();
});// 基本路由
app.get("/", (req, res) => {res.send("hello world");
});// 创建 HTTP 服务器
const http = require("http");
const server = http.createServer(app);
let once = 0;
// 在 HTTP 服务器上挂载 WebSocket 服务器
const wss = new WebSocket.Server({ server });
// 心跳检测间隔(毫秒)
const heartbeatInterval = 3000;
// 当有客户端连接时触发
wss.on("connection", (socket) => {console.log("客户端已连接...");socket.send("hello client!");// 处理收到的消息socket.on("message", (data) => {console.log(`收到客户端发送的消息: ${data}`);// 如果是心跳消息,可以不做特殊处理或者回应if (data.toString() === "Heartbeat 心跳检测") {console.log("收到心跳消息");//   // 可以选择回应心跳(回应一次)if (once < 4) {socket.send("Heartbeat_ack");once++;}// !!!!! 测试 故意不回复} else {// 处理其他消息// 这里可以添加你的业务逻辑}});
});// 监听端口
server.listen(3000, () => {console.log("服务器已经在端口3000启动");console.log("HTTP服务示例已经启动 http://localhost:3000");console.log("WebSocket服务示例已经启动 ws://localhost:3000");
});

index.html

<!DOCTYPE html>
<html><head><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>Socket.IO chat</title><style>body {margin: 0;padding-bottom: 3rem;font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;}#form {background: rgba(0, 0, 0, 0.15);padding: 0.25rem;position: fixed;bottom: 0;left: 0;right: 0;display: flex;height: 3rem;box-sizing: border-box;backdrop-filter: blur(10px);}#input {border: none;padding: 0 1rem;flex-grow: 1;border-radius: 2rem;margin: 0.25rem;}#input:focus {outline: none;}#form>button {background: #333;border: none;padding: 0 1rem;margin: 0.25rem;border-radius: 3px;outline: none;color: #fff;}#messages {list-style-type: none;margin: 0;padding: 0;}#messages>li {padding: 0.5rem 1rem;}#messages>li:nth-child(odd) {background: #efefef;}</style>
</head><body><div>前后端 Websocket 连接交互</div><button id="createBtn">创建连接</button><button id="closeBtn">断开连接</button><button id="sendBtn">发送消息(未启用)</button></body>
<script>const createBtn = document.getElementById('createBtn');const closeBtn = document.getElementById('closeBtn');const sendBtn = document.getElementById('sendBtn');// 是否手动关闭let isManualClose = false;let ws;let reconnectTimeout;// 心跳间隔const HEARTBEAT_INTERVAL = 3000;// 如果10s未响应则超时const HEARTBEAT_TIMEOUT = 10000;// 重连const RECONNECT_INTERVAL = 5000;let heartbeatInterval; // 心跳发送计时器let heartbeatTimeoutTimer; // 心跳超时检测计时器function connect() {// 如果已有连接,先关闭if (ws) {ws.close();}ws = new WebSocket('ws://localhost:3000');ws.onopen = function (e) {console.log('ws 已经连接', e);ws.send('Hello server')// 开始心跳检测startHeartbeat()}ws.onmessage = function (e) {const msg = e.data.trim();console.log("接收到的消息", msg);// 关键:仅当消息是「心跳响应」时,才重置超时计时器if (msg === "Heartbeat_ack") {startHeartbeatTimeout()}// 其他消息(如服务端的"hello client!")不处理心跳超时,避免误重置}ws.onerror = function () {console.log('WebSocket error');//如果链接错误 则关闭链接 触发重连ws.close();// 停止心跳};ws.onclose = function () {console.log('WebSocket closed');// 停止心跳stopHeartbeat();// 非主动关闭if (!isManualClose) {console.log("异常关闭,尝试重连");startReconnect()}};}createBtn.addEventListener('click', () => {isManualClose = falseconnect();})closeBtn.addEventListener('click', () => {manualClose()});// 启动重连function startReconnect() {// 清除之前的重连定时器(因为可能之前重连过)if (reconnectTimeout) {clearTimeout(reconnectTimeout);}// 检查网络状态,仅在线时重连if (!navigator.onLine) {console.log('网络离线,等待恢复...');return;}reconnectTimeout = setTimeout(() => {console.log('尝试重连...');connect(); // 重建连接}, RECONNECT_INTERVAL);}//  心跳机制function startHeartbeat() {stopHeartbeat(); // 先清理之前的心跳定时器heartbeatInterval = setInterval(function () {if (ws && ws.readyState === WebSocket.OPEN) {// 发送心跳消息ws.send('Heartbeat 心跳检测');}}, HEARTBEAT_INTERVAL); // 每3秒发送一次心跳// 启动检测startHeartbeatTimeout()}function stopHeartbeat() {if (heartbeatInterval) {clearInterval(heartbeatInterval);heartbeatInterval = null;}}// 心跳超时检测function startHeartbeatTimeout() {// debugger;console.log("心跳超时检测开始");// 重新计算clearTimeout(heartbeatTimeoutTimer);heartbeatTimeoutTimer = setTimeout(() => {// 超时未收到响应,主动断开并重连console.error('心跳超时,连接异常');ws.close(1000, 'header timeout'); // 主动关闭// 触发重连逻辑(此处省略,可参考之前的断线重连实现)}, HEARTBEAT_TIMEOUT);}// 手动关闭连接(不触发重连)function manualClose() {//停止一切操作isManualClose = true;if (ws) {ws.close(1000, 'manual close');}stopHeartbeat();}// // 监听网络状态变化window.addEventListener('online', function () {console.log('网络已连接');// 网络恢复时尝试重连if (!ws || ws.readyState === WebSocket.CLOSED || ws.readyState === WebSocket.CLOSING) {console.log('网络恢复,尝试重新连接...');isManualClose = false; // 重置手动关闭标志startReconnect();}});window.addEventListener('offline', function () {console.log('网络已断开');// 网络断开时的处理if (ws) {console.log('网络断开,WebSocket连接可能已失效');ws.close()}// 清理重连定时器if (reconnectTimeout) {clearTimeout(reconnectTimeout);}});</script></html>
http://www.dtcms.com/a/353869.html

相关文章:

  • 数据结构——抽象数据类型(ADT)
  • 浏览器渲染帧管线全景拆解:从像素到屏幕的 16.67 ms 之旅
  • Linux内核bitmap组件详解
  • 给Ubuntu添加新用户
  • MyBatis 之关联查询(一对一、一对多及多对多实现)
  • Ansible Playbook 概述与实践案例(下)
  • 基于muduo库的图床云共享存储项目(二)
  • STM32 之串口WIFI应用--基于RTOS的环境
  • AlphaFold 2 本地部署与安装教程(Linux)
  • ICCV 2025 | 清华IEDA提出GUAVA,单图创建可驱动的上半身3D化身!实时、高效,还能捕捉细腻的面部表情和手势。
  • 【51单片机】【protues仿真】基于51单片机篮球计时计分器数码管系统
  • 什么是代理ip?代理ip的运作机制
  • C++ 中 ::(作用域解析运算符)的用途
  • 大小鼠糖水偏爱实验系统 糖水偏好实验系统 小鼠糖水偏好实验系统 大鼠糖水偏好实验系统
  • 【半导体制造流程概述】
  • 优化IDEA卡顿的问题
  • 使用CCProxy搭建http/https代理服务器
  • AWS OpenSearch 可观测最佳实践
  • Maya绑定:人物绑定详细案例
  • 数据结构之 【红黑树的简介与插入问题的实现】
  • 数值分析离散积分近似求值
  • 【数据分析】微生物群落网络构建与模块划分的比较研究:SparCC、Spearman-RAW与Spearman-CLR方法的性能评估
  • Shell编程-随机密码生成
  • volitale伪共享问题及解决方案
  • SoC如何实现线程安全?
  • 【进阶篇第五弹】《详解存储过程》从0掌握MySQL中的存储过程以及存储函数
  • TypeScript:Interface接口
  • 如何启动一个分支网络改造试点?三步走
  • 【链表 - LeetCode】25. K 个一组翻转链表
  • 干眼症护理学注意事项