• 封装一个websocket,支持断网重连、心跳检测,拿来开箱即用


    封装一个websocket,支持断网重连、心跳检测

    代码封装

    编写 WebSocketClient.js

    import { EventDispatcher } from './dispatcher'
    
    export class WebSocketClient extends EventDispatcher {
      constructor(url) {
        console.log(url, 'urlurl')
        super()
        this.url = url
      }
    
      // #socket实例
      socket = null
      // #重连次数
      reconnectAttempts = 0
      // #最大重连数
      maxReconnectAttempts = 5
      // #重连间隔
      reconnectInterval = 10000 // 10 seconds
      // #发送心跳数据间隔
      heartbeatInterval = 1000 * 30
      // #计时器id
      heartbeatTimer = undefined
      // #彻底终止ws
      stopWs = false
    
      // >生命周期钩子
      onopen(callBack) {
        this.addEventListener('open', callBack)
      }
    
      onmessage(callBack) {
        this.addEventListener('message', callBack)
      }
    
      onclose(callBack) {
        this.addEventListener('close', callBack)
      }
    
      onerror(callBack) {
        this.addEventListener('error', callBack)
      }
    
      // >消息发送
      send(message) {
        if (this.socket && this.socket.readyState === WebSocket.OPEN) {
          this.socket.send(message)
        } else {
          console.error('[WebSocket] 未连接')
        }
      }
    
      // !初始化连接
      connect() {
        if (this.reconnectAttempts === 0) {
          this.log('WebSocket', `初始化连接中...          ${this.url}`)
        }
        if (this.socket && this.socket.readyState === WebSocket.OPEN) {
          return
        }
        this.socket = new WebSocket(this.url)
    
        // !websocket连接成功
        this.socket.onopen = (event) => {
          this.stopWs = false
          // 重置重连尝试成功连接
          this.reconnectAttempts = 0
          // 在连接成功时停止当前的心跳检测并重新启动
          this.startHeartbeat()
          this.log('WebSocket', `连接成功,等待服务端数据推送[onopen]...     ${this.url}`)
          this.dispatchEvent('open', event)
        }
    
        this.socket.onmessage = (event) => {
          this.dispatchEvent('message', event)
          this.startHeartbeat()
        }
    
        this.socket.onclose = (event) => {
          if (this.reconnectAttempts === 0) {
            this.log('WebSocket', `连接断开[onclose]...    ${this.url}`)
          }
          if (!this.stopWs) {
            this.handleReconnect()
          }
          this.dispatchEvent('close', event)
        }
    
        this.socket.onerror = (event) => {
          if (this.reconnectAttempts === 0) {
            this.log('WebSocket', `连接异常[onerror]...    ${this.url}`)
          }
          this.closeHeartbeat()
          this.dispatchEvent('error', event)
        }
      }
    
      // > 断网重连逻辑
      handleReconnect() {
        if (this.reconnectAttempts < this.maxReconnectAttempts) {
          this.reconnectAttempts++
          this.log(
            'WebSocket',
            `尝试重连... (${this.reconnectAttempts}/${this.maxReconnectAttempts})       ${this.url}`
          )
          setTimeout(() => {
            this.connect()
          }, this.reconnectInterval)
        } else {
          this.closeHeartbeat()
          this.log('WebSocket', `最大重连失败,终止重连: ${this.url}`)
        }
      }
    
      // >关闭连接
      close() {
        if (this.socket) {
          this.stopWs = true
          this.socket.close()
          this.socket = null
          this.removeEventListener('open')
          this.removeEventListener('message')
          this.removeEventListener('close')
          this.removeEventListener('error')
        }
        this.closeHeartbeat()
      }
    
      // >开始心跳检测 -> 定时发送心跳消息
      startHeartbeat() {
        if (this.stopWs) return
        if (this.heartbeatTimer) {
          this.closeHeartbeat()
        }
        this.heartbeatTimer = setInterval(() => {
          if (this.socket) {
            this.socket.send(JSON.stringify({ type: 'heartBeat', data: {} }))
            this.log('WebSocket', '送心跳数据...')
          } else {
            console.error('[WebSocket] 未连接')
          }
        }, this.heartbeatInterval)
      }
    
      // >关闭心跳
      closeHeartbeat() {
        clearInterval(this.heartbeatTimer)
        this.heartbeatTimer = undefined
      }
    }
    

    引用的 dispatcher.js 源码

    import { Log } from './log'
    
    export class EventDispatcher extends Log {
      constructor() {
        super()
        this.listeners = {}
      }
    
      addEventListener(type, listener) {
        if (!this.listeners[type]) {
          this.listeners[type] = []
        }
        if (this.listeners[type].indexOf(listener) === -1) {
          this.listeners[type].push(listener)
        }
      }
    
      removeEventListener(type) {
        this.listeners[type] = []
      }
    
      dispatchEvent(type, data) {
        const listenerArray = this.listeners[type] || []
        if (listenerArray.length === 0) return
        listenerArray.forEach((listener) => {
          listener.call(this, data)
        })
      }
    }
    

    上面还用到了一个 log.js ,用于美化控制台打印的,这个文件在其他地方也通用

    export class Log {
      static console = true
    
      log(title, text) {
        if (!Log.console) return
        const color = '#09c'
        console.log(
          `%c ${title} %c ${text} %c`,
          `background:${color};border:1px solid ${color}; padding: 1px; border-radius: 2px 0 0 2px; color: #fff;`,
          `border:1px solid ${color}; padding: 1px; border-radius: 0 2px 2px 0; color: ${color};`,
          'background:transparent'
        )
      }
    
      closeConsole() {
        Log.console = false
      }
    }
    

    至此一个 WebSocket 就封装好了

    使用方法

    首先使用node编写一个后端服务,用于 WebSocket 连接

    需要安装一下 ws

    npm install ws
    
    const WebSocket = require("ws");
    
    const wss = new WebSocket.Server({port: 3200});
    
    console.log("服务运行在http://localhost:3200/");
    
    wss.on("connection", (ws) => {
        console.log("[服务器]:连接成功");
        ws.send(`[websocket云端]您已经连接云端!等待数据推送~`);
    
        ws.on("message", (res) => {
            ws.send(`[websocket云端]收到消息:${res.toString()}`);
        });
    
        ws.on("close", () => {
            console.log("[服务器]:连接已关闭~");
        });
    });
    

    然后我这里编写了一个简单的demo页面

    <template>
      <div>
        <el-button type="primary" @click="connection">创建连接el-button>
        <el-button type="danger" @click="close">关闭连接el-button>
    
        <el-input v-model="message" placeholder="placeholder">el-input>
        <el-button type="primary" @click="send">发送消息el-button>
    
        <ul>
          <li v-for="(item, index) in messageList" :key="index">{{ item }}li>
        ul>
      div>
    template>
    
    <script>
    import { WebSocketClient } from '@/utils/WebSocketClient'
    
    export default {
      data() {
        return {
          message: '',
          messageList: [],
          ws: null,
        }
      },
      methods: {
        connection() {
          if (this.ws) {
            this.close()
          }
          this.ws = new WebSocketClient('ws://localhost:3200')
          this.setupWebSocketListeners()
          this.ws.connect()
        },
        close() {
          if (this.ws) {
            this.ws.close()
            this.ws = null
          }
        },
        send() {
          if (this.ws) {
            this.ws.send(this.message)
          }
        },
        setupWebSocketListeners() {
          this.ws.onmessage((msg) => {
            this.ws.log('WebSocketClient', msg.data)
            this.messageList.push(msg.data)
          })
          this.ws.onopen(() => {
            this.ws.log('WebSocketClient', '连接已打开')
          })
          this.ws.onclose(() => {
            this.ws.log('WebSocketClient', '连接已关闭')
          })
          this.ws.onerror((error) => {
            this.ws.log('WebSocketClient', '连接错误')
            console.error(error)
          })
        },
      },
      mounted() {
        this.connection()
      },
    }
    script>
    

    初次连接

    image-20240531100922169

    消息发送

    image-20240531100944165

    关闭连接后,消息就无法发送了

    image-20240531101029443

    再次连接

    image-20240531101057517

  • 相关阅读:
    App备案-iOS云管理式证书 Distribution Managed 公钥及证书SHA-1指纹的获取方法
    vscode文件夹折叠问题
    ffmpeg视频处理常用命令
    webrtc USB摄像头
    promise执行顺序面试题令我头秃,你能作对几道
    大数据学习系列之一 ----- Hadoop环境搭建(单机)
    Redis快速入门
    Location的匹配
    Rust 从入门到精通05-数据类型
    Qt调用sqlserver的存储过程
  • 原文地址:https://blog.csdn.net/SongZhengxing_/article/details/139345257