当前位置:   article > 正文

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

封装一个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
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148

引用的 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)
    })
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

上面还用到了一个 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
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

至此一个 WebSocket 就封装好了

使用方法

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

需要安装一下 ws

npm install ws
  • 1
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("[服务器]:连接已关闭~");
    });
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

然后我这里编写了一个简单的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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

初次连接

image-20240531100922169

消息发送

image-20240531100944165

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

image-20240531101029443

再次连接

image-20240531101057517

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/660478
推荐阅读
  

闽ICP备14008679号