简易的 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');
})
相关推荐
hzyyyyyyyu38 分钟前
内网安全隧道搭建-ngrok-frp-nps-sapp
服务器·网络·安全
刽子手发艺1 小时前
WebSocket详解、WebSocket入门案例
网络·websocket·网络协议
速盾cdn5 小时前
速盾:CDN是否支持屏蔽IP?
网络·网络协议·tcp/ip
yaoxin5211235 小时前
第二十七章 TCP 客户端 服务器通信 - 连接管理
服务器·网络·tcp/ip
内核程序员kevin5 小时前
TCP Listen 队列详解与优化指南
linux·网络·tcp/ip
PersistJiao7 小时前
Spark 分布式计算中网络传输和序列化的关系(一)
大数据·网络·spark
黑客Ash9 小时前
【D01】网络安全概论
网络·安全·web安全·php
->yjy9 小时前
计算机网络(第一章)
网络·计算机网络·php
摘星星ʕ•̫͡•ʔ10 小时前
计算机网络 第三章:数据链路层(关于争用期的超详细内容)
网络·计算机网络