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

news/2024/9/18 6:31:11/ 标签: websocket, 网络协议, 网络

文章目录

  • 演示
  • 大纲
  • 基础 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.5console.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.ppmy.cn/news/1518176.html

相关文章

自动化通过cmd命令行并以特定账户连接到远程机器

1 建一个taskschedule运行cmd命令 2 cmd命令示例&#xff1a; 机器名加域名 mstsc /v:"<MachineName>.xxx.xxx.com"或者是机器IP地址 mstsc /v:"10.148.66.178"3 设置用特定账户登陆 用户名可以写 <要连接的机器名><用户名> 勾选“记住…

达梦数据库启动与停止

1.1.1数据库启停之菜单方式启动、停止达梦数据库 当数据库服务器是Windows时&#xff0c;开始-->达梦数据库-->点击“DM服务查看器”&#xff0c;找到 “DmService【数据库实例名】” -->右键启动或停止。 下图中数据库实例名是DMSERVER 当数据库服务器是Linux时&…

工程数学与数学建模在编程与算法设计中的应用(下)

目录 引言 第三部分&#xff1a;工程数学在算法设计与优化中的应用 3.1 微分方程与动力系统模拟 常微分方程&#xff08;ODE&#xff09;在动态系统中的应用 偏微分方程&#xff08;PDE&#xff09;在图像处理与物理模拟中的应用 总结 3.2 概率论与数理统计在机器学习中的…

解决firewalld启动状态下docker无法启动

环境&#xff1a;centos 7 docker安装方式&#xff1a;二进制文件安装&#xff0c;点击跳转安装方法链接 docker版本&#xff1a;27.2.2 问题描述&#xff1a;按照原来的二进制安装部署方式&#xff0c;到了最后一步&#xff1a; systemctl start docker 发现一直卡住不动&…

【CVPR‘24】BP-Net:用于深度补全的双边传播网络,新 SOTA!

【CVPR24】BP-Net:用于深度补全的双边传播网络,新 SOTA! 摘要介绍方法1. 总体架构2. 双边传播模块(Bilateral Propagation Module)深度参数化参数生成先验编码3. 多模态融合(Multi-modal Fusion)4. 深度细化(Depth Refinement)5. 损失函数结果与分析结论论文地址:htt…

C++map容器中operator[ ]的实现原理

目录 一、operator[ ]函数介绍 二、insert函数介绍 三、operator[ ]函数实现原理 四、operator[ ]函数功能 一、operator[ ]函数介绍 mapped_type& operator[] (const key_type& k);在map容器中存储的是一个键值对value_type&#xff0c;其本质是pair<const key…

【C#】【EXCEL】Bumblebee/Classes/ExRange.cs

Flow diagram 为了创建一个全面但不过于复杂的流程图&#xff0c;我们将重点关注 ExRange 类的主要功能和方法。以下是一个中英双语的流程图&#xff0c;展示了 ExRange 类的主要结构和操作流程&#xff1a; #mermaid-svg-SYKM6gZKQKi0qEx1 {font-family:"trebuchet ms&q…

uniapp,uview:inputnumber或者input,当type为number的时候,在ios里输入不了小数的问题

项目场景&#xff1a; 在做uniapp的H5页面时&#xff0c;有个需求是要输入框要能支持可以保留两位小数输入&#xff0c;不能输入负数和其他字符。心想这简单&#xff0c;直接用uview的inputnumber组件这不就好了&#xff0c;结果测试提bug说不能输入小数点&#xff0c;我心想我…

【AI绘画教程】老照片修复全攻略!AI绘图工具SD一键将老照片高清修复,让你的青春岁月重泛光彩!

大家好&#xff0c;我是画画的小强 对于很多黑白老照片来说&#xff0c;最主要的修复内容包括分辨率的放大和对照片上色&#xff0c;在AI绘图工具 SD WebUI中有不少分辨率放大的方式&#xff0c;对老照片修复来说&#xff0c;采用Tiled DiffusionContraolNet Tile的组合效果是…

cucumber 怎么启动API

Cucumber是一个行为驱动开发&#xff08;BDD&#xff09;测试框架&#xff0c;它可以用来定义和执行测试用例。 启动API通常意味着你需要先启动你的API服务器&#xff0c;然后通过Cucumber执行测试用例来测试API的行为。 以下是一个简单的步骤来使用Cucumber启动API&#xff1…

【Linux】深入探讨Linux进程等待:`waitpid`与`wait`

文章目录 深入探讨Linux进程等待&#xff1a;waitpid与wait API一、waitpid与wait简介1. wait2. waitpid 二、waitpid与wait的实际应用1. 基本用法示例2. 使用 waitpid 处理多个子进程3. 非阻塞等待 三、使用场景 深入探讨Linux进程等待&#xff1a;waitpid与wait API 在Linux…

力扣(无重叠区间)

435. 无重叠区间 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互不重叠 。 思路&#xff1a; 先按照左侧数字排序&#xff0c;然后对于i位置和i1位置&#xff0c;如果i位置右侧数字大于…

基于detectron2框架的深度学习模型载入自定义数据集

基于detectron2框架的深度学习模型载入自定义数据集 一、前言 最近在做微光目标检测的研究工作&#xff0c;使用了Rank_DETR&#xff1b;这个模型是基于detrex框架&#xff0c;而detrex框架又是基于detectron2的。找了一圈没找到载入数据集的地方&#xff0c;后面查阅了资料得…

【数据结构4】树的实例-模拟文件系统、二叉树的遍历(先序遍历、中序遍历、后序遍历、层次遍历)

1 树和二叉树 2 树的实例-模拟文件系统 3 二叉树 3.1 二叉树的遍历 二叉树的先序遍历 二叉树的中序遍历 二叉树的后序遍历 二叉树的层次遍历 1 树 树是一种数据结构 比如:目录结构 树是一种可以递归定义的数据结构树是由n个节点组成的集合:如果n0&#xff0c;那这是一棵空树;如…

微服务实战系列之玩转Docker(十二)

前言 山一程&#xff0c;水一程&#xff0c;身向榆关那畔行&#xff0c;夜深千帐灯。——清纳兰性德 最近偶读纳兰的《长相思》经典之作&#xff0c;被这个“行军”场面震撼了。长长的队伍&#xff0c;跋山涉水&#xff0c;野宿一处。夜深人静的时候&#xff0c;突然激发了纳兰…

noge-gyp构建项目踩坑记录

开发环境 系统: win11 node: 19.7.0 npm: 8.3.2 node-gyp: 10.0.2可以不使用windows-build-tools来安装构建工具,手动进行安装 我这边用windows-build-tools安装时候会提示 process.env only accepts a configurable, writable, and enumerable data descriptor. 查了资料后…

纯vue实现笔记系统

前言 最近研究了一个笔记记录系统&#xff0c;然后突然想到一个问题&#xff0c;我该如何才能只用前端就实现笔记的记录系统&#xff1f;经过这两天的研究将其做出来了&#xff0c;接下来将分享实现的过程 ✨✨✨✨✨✨✨✨✨✨ 项目演示 在我的项目中&#xff0c;是可以适…

Spring系列之设计模式

Spring作为Java世界当之无愧的No.1开源框架&#xff0c;其架构设计非常优秀&#xff0c;使用很多设计模式。 注&#xff1a; 本文参考Spring-6.1.5版本源码&#xff1b;本文目录设置参考设计模式三大类来划分&#xff0c;Spring家族包括Spring、Spring MVC、Spring Boot、Spr…

prometheus download all

prometheus 前言 基于prometheus 数据采集,MySQL,sql_server,Redis,Rabbitmq,snmp,容器,告警模块,Winodws,Linux,模板包,离线包 prometheus download all 其它采集模块参考版本官网百度网盘离线alertmanager download百度网盘

从零开发一个vscode插件

nodejs建议使用 LTS 版本yeoman脚手架工具 npm install -g yo generator-codeVSCode代码生成器 npm install -g generator-code 搭建环境 执行下面代码 yo code 通过yo code生成插件开发项目&#xff0c;这里官方推荐使用TypeScript&#xff0c;当然我们更熟悉javascrip…