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

文章目录

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

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

演示


大纲

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

基础 WebSocket

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

html 复制代码
<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,其他逻辑保持不变

js 复制代码
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

前端 代码如下

html 复制代码
<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>

前端: 尝试重新连接

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

历史代码

前端

html 复制代码
<!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>

后端

js 复制代码
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');
})
相关推荐
萧瑟余晖8 分钟前
Netty 核心组件与 Reactor 模型详解
网络·架构
ITxiaobing202319 分钟前
IP 定位服务选型指南:从准确率到工程落地的技术考察
linux·服务器·网络
wuyk55521 分钟前
【Socket 进阶之路】第 9 章 Linux 网络服务量产稳定性优化|心跳保活、TIME_WAIT、SO_LINGER、内存池、断线重连、完整异常防护框架
linux·服务器·开发语言·网络·物联网
z落落34 分钟前
C#UDP+串口服务端+UDP 客户端(含 CRC16 校验)
网络·网络协议·udp
云运维笔记1 小时前
华为设备IP地址配置全攻略
运维·网络·计算机网络·华为
爱研究的小梁2 小时前
告别实验室理想网络,真实场景下具身智能远程操控怎么干?
网络·人工智能·机器人·信息与通信
程序员若风2 小时前
CSRF攻击原理介绍和利用-腾讯云开发者社区
前端·网络·安全·web安全·网络安全·csrf攻击·腾讯云开发者社区
曼巴UE52 小时前
UE5 客户端 需要的网络同步概念总结(3 )-事件同步 RPC 以及三种调用方式
网络·c++·网络协议·学习·rpc·ue5
captain3762 小时前
HTTP(2)
java·网络·http·java-ee
万联WANFLOW2 小时前
94%成交引爆美区:TikTok直播电商迎来高客单价时代
网络·业界资讯