封装一个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

相关推荐

  1. WebSocket 心跳检测功能封装

    2024-06-08 02:06:01       8 阅读
  2. uniapp封装websocket以及心跳检测

    2024-06-08 02:06:01       13 阅读
  3. vue封装websocket以及心跳检测

    2024-06-08 02:06:01       15 阅读
  4. 支Vue3的WebSocket插件并支持线

    2024-06-08 02:06:01       43 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-08 02:06:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-08 02:06:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-08 02:06:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-08 02:06:01       20 阅读

热门阅读

  1. Vue3视图渲染技术

    2024-06-08 02:06:01       6 阅读
  2. Python怎么把数据从CSV文件导入到MySQL数据库?

    2024-06-08 02:06:01       12 阅读
  3. 【Qt快速入门(二)】- Qt 整体目录结构

    2024-06-08 02:06:01       12 阅读
  4. 深入探讨Qt中的QVariant

    2024-06-08 02:06:01       10 阅读
  5. ChatGPT的基本原理

    2024-06-08 02:06:01       8 阅读
  6. 是时候让临床预测模型进入临床实践

    2024-06-08 02:06:01       10 阅读
  7. Elasticsearch搜索优化-自定义路由规划(routing)

    2024-06-08 02:06:01       10 阅读
  8. Linux系统管理磁盘管理004

    2024-06-08 02:06:01       11 阅读
  9. 【copy_dwg_files.bat】

    2024-06-08 02:06:01       10 阅读
  10. 奇思妙想02-高考

    2024-06-08 02:06:01       12 阅读