简易的 Websocket + 心跳机制 + 尝试重连

news2024/11/15 10:43:18

文章目录

  • 演示
  • 大纲
  • 基础 WebSocket
  • 前端: 添加心跳机制
  • 前端: 尝试重新连接
  • 历史代码


还没有写完,bug 是有的,我在想解决办法了…

演示

请添加图片描述


大纲

  • 基础的 webSocket 连接
  • 前后端:添加心跳机制
  • 后端无心跳反应,前端尝试重新连接
  • 设置重新连接次数,超过最大尝试次数之后,不再尝试重新连接

基础 WebSocket

前端的基础就是这些,大概的效果是这样的

请添加图片描述

<body>
  <button onclick="reConnect()">1. 重建连接</button>
  <button onclick="sendMessage()">2. 发消息</button>
  <button onclick="stopConnect()">3. 断开连接</button>
</body>
<script>
  let ws = null // 使用null来标记当前没有活动的 WebSocket 连接

  function createNewWebSocket() {
    if (ws && ws.readyState !== WebSocket.CLOSED) {
      ws.close() // 确保关闭旧的连接
    }
    ws = new WebSocket('ws://localhost:8080')

    ws.onopen = function (evt) {
      console.log('Connection open ...')
    }

    ws.onmessage = function (evt) {
      console.log('Received Message: ' + evt.data)
    }

    ws.onclose = function (evt) {
      console.log('Connection closed.')
    }
  }

  function sendMessage() {
    if (ws) ws.send(`前端发送:>> ${new Date()}`)
  }

  function stopConnect() {
    if (ws) ws.close()
  }

  function reConnect() {
    createNewWebSocket()
  }
</script>

后端的代码基本不变,所以我直接把心跳也做好
后端的心跳就是:拿到前端的值,如果是 ping 的话,就返回一个 pong,其他逻辑保持不变

const http = require('http')
const WebSocket = require('ws')
const server = http.createServer()
const wss = new WebSocket.Server({ server })

wss.on('connection', (socket) => {
  console.log('webSocket 连接成功')

  socket.on('message', (message) => {
    // 将 Buffer 转换为字符串
    const messageStr = message.toString()
    const currentRandom = Math.random()
    const isSendPong = currentRandom < 0.5
    console.log('后端收到消息:>>' + messageStr)
    // 检查是否为心跳请求
    if (messageStr === 'ping') {
      socket.send(`当前随机值为 ${currentRandom}, 是否发送心跳:${isSendPong}`)
      //  50%的概率发送 "pong"
      if (isSendPong) {
        socket.send('pong') // 心跳响应
      }
    } else {
      const message = `后端发送消息:>> 你好前端~ ${new Date().toLocaleString()}`
      socket.send(message)
    }
  })

  socket.on('close', () => {
    console.log('websocket 已经关闭')
  })
})

server.on('request', (request, response) => {
  response.writeHead(200, { 'Content-Type': 'text/plain' })
  response.end('Hello, World')
})

server.listen(8080, () => {
  console.log('服务器已启动,端口号为 8080')
})


前端: 添加心跳机制

思路:前端写一个定时器,用于隔一段时间发送一个 ping
效果如下图所示请添加图片描述

好吧,false 的概率有点高,不顾可以看历史记录

我在后端设置了随机逻辑,模拟一下出错的场景,百分之50的概率回应前端的心跳,如果 true 的话,后端就回应前端的心跳,返回 pong

前端 代码如下

<body>
  <button onclick="reConnect()">1. 重建连接</button>
  <button onclick="sendMessage()">2. 发消息</button>
  <button onclick="stopConnect()">3. 断开连接</button>
</body>
<script>
  let ws = null // 使用null来标记当前没有活动的 WebSocket 连接
  let heartbeatTimer = null // 心跳定时器
  const HEARTBEAT_INTERVAL = 5000 // 每隔 5 秒发送一次心跳

  function createNewWebSocket() {
    if (ws && ws.readyState !== WebSocket.CLOSED) {
      ws.close() // 确保关闭旧的连接
    }
    ws = new WebSocket('ws://localhost:8080')

    ws.onopen = function (evt) {
      console.log('Connection open ...')
      startHeartbeat()
    }

    ws.onmessage = function (evt) {
      console.log('Received Message: ' + evt.data)
      handleHeartbeatResponse(evt.data)
    }

    ws.onclose = function (evt) {
      console.log('Connection closed.')
      stopHeartbeat()
    }
  }

  function sendMessage() {
    if (ws) ws.send(`前端发送:>> ${new Date().toLocaleString()}`)
  }

  function stopConnect() {
    if (ws) ws.close()
    stopHeartbeat()
  }

  function reConnect() {
    createNewWebSocket()
  }

  function startHeartbeat() {
    heartbeatTimer = setInterval(() => {
      ws.send('ping')
    }, HEARTBEAT_INTERVAL)
  }

  function stopHeartbeat() {
    clearInterval(heartbeatTimer)
    heartbeatTimer = null
  }

  function handleHeartbeatResponse(message) {
    if (message === 'heartbeat') {
      console.log('Heartbeat received.')
      clearTimeout(heartbeatTimer) // 清除超时定时器
      startHeartbeat() // 重新启动心跳
    }
  }
</script>

前端: 尝试重新连接

设置一个场景: 前端发送三个心跳包,如果都没有反应,那么就判断为断开连接了,就去重新连接

历史代码

前端

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button id="btn">发消息</button>
    <button id="stop">断链</button>
    <button id="reconnect">重新连接</button>
  </body>
  <script>
    const btn = document.querySelector('#btn')
    const stop = document.querySelector('#stop')
    const reconnect = document.querySelector('#reconnect')
    let ws = null // 使用null来标记当前没有活动的WebSocket连接
    let heartbeatInterval = null // 存储心跳定时器
    let timeoutId = null // 存储超时定时器
    let reconnectAttempts = 0 // 重连尝试次数
    const maxReconnectAttempts = 3 // 最大重连次数

    function createNewWebSocket() {
      if (ws && ws.readyState !== WebSocket.CLOSED) {
        ws.close() // 确保关闭旧的连接
      }
      ws = new WebSocket('ws://localhost:8080')

      ws.onopen = function (evt) {
        console.log('Connection open ...')
        startHeartbeat() // 开始发送心跳
      }

      ws.onmessage = function (evt) {
        console.log('Received Message: ' + evt.data)

        // 检查是否为心跳响应
        if (evt.data === 'pong') {
          clearTimeout(timeoutId) // 清除超时定时器
          resetHeartbeatTimer() // 重置心跳定时器
        }
      }

      ws.onclose = function (evt) {
        console.log('Connection closed.')
        clearInterval(heartbeatInterval) // 清除心跳定时器
        reconnectWebSocket() // 尝试重新连接
      }
    }

    // 发送心跳包
    function sendHeartbeat() {
      if (ws) {
        ws.send('ping')
        timeoutId = setTimeout(() => {
          // 如果在超时时间内没有收到 "pong" 响应,则关闭当前连接
          console.log('超时,关闭连接')
          ws.close()
        }, 15000) // 设置超时时间为 15 秒
      }
    }

    // 启动心跳
    function startHeartbeat() {
      sendHeartbeat() // 立即发送第一个心跳包
      heartbeatInterval = setInterval(sendHeartbeat, 30000) // 每30秒发送一次
    }

    // 重置心跳定时器
    function resetHeartbeatTimer() {
      clearInterval(heartbeatInterval)
      heartbeatInterval = setInterval(sendHeartbeat, 30000) // 重新设置定时器
    }

    // 重新连接
    function reconnectWebSocket() {
      console.log('尝试重新连接', reconnectAttempts)

      // 检查是否超过最大重连次数
      if (reconnectAttempts < maxReconnectAttempts) {
        reconnectAttempts++
        createNewWebSocket()
      } else {
        console.log('超过最大重连次数,不再尝试连接')
      }
    }

    btn.addEventListener('click', () => {
      if (ws) {
        ws.send(`前端发送:>> ${new Date()}`)
      }
    })

    stop.addEventListener('click', () => {
      if (ws) {
        ws.close()
      }
    })

    reconnect.addEventListener('click', () => {
      createNewWebSocket()
    })
  </script>
</html>

后端

const http = require('http')
const WebSocket = require('ws')
const server = http.createServer()
const wss = new WebSocket.Server({ server })

wss.on('connection', (socket) => {
  console.log('webSocket 连接成功')

  socket.on('message', (message) => {
    // 将 Buffer 转换为字符串
    const messageStr = message.toString();

    console.log('后端收到消息:>>' + messageStr);

    // 检查是否为心跳请求
    if (messageStr === 'ping') {
      socket.send('pong'); // 心跳响应
    } else {
      socket.send('后端发送消息:>> hello 我是 socket.send');
    }
  })

  socket.on('close', () => {
    console.log('websocket 已经关闭');
  })
})

server.on('request', (request, response) => {
  response.writeHead(200, { 'Content-Type': 'text/plain' });
  response.end('Hello, World');
})

server.listen(8080, () => {
  console.log('服务器已启动,端口号为 8080');
})

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2069012.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java 日常反常识踩坑

作者&#xff1a;若渝 本文主要是日常业务开发中自身碰到过跟常识不一致的坑&#xff0c;问题虽然基础&#xff0c;但却可能造成比较大的线上问题。 一、转 BigDecimal 类型时精度丢失 public class Test { public static void main(String[] args) { BigDecimal bi…

算法-分隔链表

一、题目描述 (一) 题目 给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。你应当保留两个分区中每个节点的初始相对位置。 (二) 示例 示例 1&#xff1a; 输入&#xff1a;…

用Python实现9大回归算法详解——07. 支持向量机回归算法

1. 支持向量机回归的基本概念 支持向量机回归&#xff08;Support Vector Regression, SVR&#xff09;是支持向量机&#xff08;SVM&#xff09;的一个应用&#xff0c;主要用于回归任务。与分类任务中的 SVM 类似&#xff0c;SVR 通过找到一个最大化边界&#xff08;即支持向…

[WUSTCTF2020]spaceclub

上sublime txt 每一行的长短对应一个二进制位&#xff0c;长空格是1&#xff0c;短空格是0&#xff0c;全部替换掉得到 上python脚本 import binasciiwith open(attachment_5.txt, r) as file:lines file.readlines() # 逐行读取文本内容output # 初始化输出字符串# 遍历…

vscode 写了未定义的方法不报错,配置全局ESLint

最近接触了一个旧的vue2的项目&#xff0c;里面没有ts和eslint配置 在正在维护的页面里复制了其他页面的一个方法&#xff0c;方法里面包含lodash的cloneDeep&#xff0c;cloneDeep在这个页面并没有引入&#xff0c;但是vscode却没有提示&#xff0c;很不友好&#xff0c;容易…

JUC阻塞队列(五):SynchronousQueue

1、SynchronousQueue介绍 SynchronousQueue与前边的其他几个阻塞队列的差异是挺大的&#xff0c;在一般逻辑中队列是一个用 来存储数据的中间容器&#xff08;前边几个阻塞队列也是用来存放数据的&#xff09;&#xff0c;但SynchronousQueue 却不是用来存放数据的&#xff0c;…

自动控制——用描述函数法分析非线性系统的稳定性与自激振荡

用描述函数法分析非线性系统的稳定性与自激振荡 引言 在控制系统中&#xff0c;非线性系统的稳定性和自激振荡&#xff08;self-oscillation&#xff09;问题往往较线性系统更为复杂。为了分析这些问题&#xff0c;描述函数法&#xff08;Describing Function Method&#xf…

QtWebEngineView加载本地网页

直接加载放在exe同级目录下的资源是不行的&#xff0c;需要把资源通过qrc放到exe里面&#xff0c;然后通过类似qrc:/robotHtml/index.html这样的路径加载才行。 mWebView new QWebEngineView(parent);// mWebView->load(QUrl::fromLocalFile("./robotHtml/index.html&…

Vue3集成高德离线地图实践

1. 离线地图效果预览 2. 地图下载器下载离线地图 根据需要选择地图&#xff0c;我这边选择高德地图&#xff0c;层级选择0-15级别即可&#xff0c;进行下载 3. 放到nginx内网服务器 注意配置允许跨域 4. Vue3核心代码 // main.js // 初始化vue-amap initAMapApiLoader({o…

联想LJ2405打印机清零方法

联想LJ2405D_LJ2455D_LJ2605D硒鼓清零方法 在设备待机状态下&#xff0c;打开前盖&#xff0c;然后按住开始键不松手&#xff0c;直到所有指示灯全部亮起后再松开手&#xff0c;然后将硒鼓取出再装回&#xff0c;盖上前盖&#xff0c;清零操作完成。 联想LJ2405打印机碳粉清零…

编程学习之路:如何克服挫折感,成为更好的自己

目录 编程学习之路&#xff1a;如何克服挫折感&#xff0c;成为更好的自己 一、小瓜有话说 1、学习的广度可以带动深度 2、清空大脑和清空代码都是解决问题的方式 ①清空大脑&#xff1a;睡个觉&#xff0c;拉个屎&#xff0c;吃顿饭。 ②清空代码&#xff1a;换一种思维…

花钱买不到系列-深刻理解进程地址空间

花钱买不到系列—linux虚拟地址空间-CSDN博客https://blog.csdn.net/weixin_49529507/article/details/141272458?spm1001.2014.3001.5501 在上一篇文章中&#xff0c;引出了虚拟地址这块&#xff0c;也用大富翁给儿子们画饼的例子解释&#xff0c;通过大富翁的例子&…

内存管理篇-03物理内存管理-32位

正片从现在开始了。 1.结构体关联 当DDR初始化后&#xff0c;整个内存就可以访问了。但是需要合理的管理&#xff0c;防止内存碎片以及安全相关的问题。因此需要对物理内存进行严格的管理。 物理内存分为&#xff1a;页&#xff0c; 分区&#xff0c;内存节点。DMA需要连续的内…

配置PXE预启动执行环境:Kickstart自动化无人值守安装

文章目录 实现 Kickstart 无人值守安装1. 安装Kickstart和配置应答文件&#xff08;图形化界面&#xff09;2. 配置 PXE 菜单支持 Kickstart 无人值守安装3. 验证 Kickstart 无人值守安装4. 拓展&#xff1a;命令行配置应答文件&#xff08;命令行界面&#xff09; 实现 Kickst…

猜数3次-python

题目要求&#xff1a; 定一个数字&#xff08;1-10&#xff0c;随机产生&#xff0c;通过3次判断来猜出数字&#xff09; 数字随机产生&#xff0c;范围1-10有三次机会猜测数字&#xff0c;通过3层嵌套判断实现每次猜不中会提示大了或者小了 ps&#xff1a;补充随机函数 imp…

client网络模块的开发和client与server端的部分联动调试

客户端网络模块的开发 我们需要先了解socket通信的流程 socket通信 server端的流程 client端的流程 对于closesocket()函数来说 closesocket()是用来关闭套接字的,将套接字的描述符从内存清除,并不是删除了那个套接字,只是切断了联系,所以我们如果重复调用,不closesocket()…

合合信息文档解析Coze插件发布,PDF转Markdown功能便捷集成

近日&#xff0c;TextIn开发的PDF转Markdown插件正式上架Coze平台。 在扣子搜索“pdf转markdown”&#xff0c;或在Coze平台搜索“pdf2markdown”&#xff0c;即可找到插件&#xff0c;在你的专属智能体中便捷使用文档解析功能。 如果想测试解析插件在你需要的场景下表现如何&…

R语言VAR模型的多行业关联与溢出效应可视化分析

全文链接&#xff1a;https://tecdat.cn/?p37397 摘要&#xff1a;本文对医疗卫生、通信、金融、房地产和零售等行业的数据展开深入研究。通过读取数据、计算收益率、构建 VAR 模型并进行估计&#xff0c;帮助客户进一步分析各行业变量的影响及残差的协方差与相关矩阵&#xf…

xml打印模板解析-SAAS本地化及未来之窗行业应用跨平台架构

一、为何要自己设置打印模板系统 1.确保自由知识产权 2.支持跨平台&#xff1a;物联网&#xff0c;自助终端&#xff0c;电脑&#xff0c;web&#xff0c;C#&#xff0c;jsp,android,java,php 等多种语言 二、xml 代码解析 package CyberWinPHP.Cyber_Plus;import java.io.…

2024下半年软考有哪些科目开考?该怎么选?

近年来&#xff0c;软考&#xff08;软件水平考试&#xff09;的难度逐渐攀升&#xff0c;这并非源于题目本身的复杂化&#xff0c;而是官方对通过率的调控策略所致。整体通过率维持在13%左右&#xff0c;高级别考试更是低至10%以下&#xff0c;考生需慎重对待。以湖南2024年上…