WebSocket 心跳机制封装

178 阅读4分钟

文章推荐:

Vue3 watch 的六大特点

Vue 自定义指令directive实现v-lazy

WebSocket 特点作用和特点回顾

WebSocket 已经成为现代化非常流行的技术。 因为他非常强大,能从客户端发送消息到服务端,也能从服务端发送消息到客户端,实现双向通讯。非常便利。在WebSocket 技术流行之前,要实现实时的通讯只能通过轮询向服务端发送消息来检测有没有新消息。但是这有很大的问题。

(1)消息不及时,

(2) 非常消耗资源,因为需要不断发送请求

于是在有了WebSocet 技术之后, 有需要即时通讯的需求,基本上都会选择WebSocket

那么Websocket 有哪些优缺点呢?

优点:

1. 节约流量,只需要一次握手

2. 通讯即时

3. 跨平台

4. 较少的开销

缺点:

  1. 比较老的浏览器不支持(不过现在这个年代基本上可以忽略不计了)
  2. 连接较多的时候服务端会有一定压力

WebSocket 心跳机制封装

说完了Websocket 的特点和作用,开始进入今天的主题了,WebSocket 心跳机制.

为什么Websocket 需要封装心跳机制呢?

因为WebSocket 在长时间(一般一分钟以上)没有消息发送的时候会断掉,断掉之后会导致消息发送不成功。

心跳机制的作用就主要是为了保持连接状态。来思考下心跳机制需要做什么?

思考了一分钟:

(1)需要定时发送一个心跳机制的消息类型, 服务端收到心跳消息回复, 前端做处理,判断为收到服务端返回的心跳消息不做处理

(2)如果连接关闭了,进行重连

(3)如果出现异常了进行重连

来看看代码的实现:

新建一个utils 文件夹,再新建ws文件

let heartbeatTimer = null
let reneectionTimer = null
let cfun = null // 重连时更新ws实例的回调
export default class Ws extends WebSocket {
  constructor (url) {
    super(url)
    this.wsUrl = url
    this.init()
    this.fun = null
  }

  init() {
    this.bindEvent()
  }
  static create (url, updateWs) {
    cfun = updateWs ? updateWs : cfun
    return new Ws(url, updateWs)
  }

  bindEvent () {
    this.addEventListener('open', this.handleOpen, false)
    this.addEventListener('close', this.handleClose, false)
    this.addEventListener('message', this.handleMessage, false)
    this.addEventListener('error', this.handleError, false)
  }

  handleOpen () {
    console.log('handleOpen')
    this.startHeartbeat()
  }

  handleClose () {
    if (heartbeatTimer) {
      clearInterval(heartbeatTimer)
      heartbeatTimer = null
    }
    if (reneectionTimer) {
      clearTimeout(reneectionTimer)
    }
    console.log('连接关闭')
    this.reconnect()
  }

  closeWs () {
    this.close()
  }

  handleMessage (data) {
    const data2 = JSON.parse(data.data)
    console.log(data2, 'data2')
    switch(data2.mode) {
      case 'heartbeat':
        console.log('---heartbeat--')
        break;
      case 'MESSAGE':
        this.fun(data2)
        break;
      default:
        break;
    }
  }

  sendMessage(msg, fun) {
    console.log(typeof msg)
    this.send(msg)
    this.fun = fun
  }

  handleError () {
    this.reconnect()
  }

  reconnect() {
    console.log('--5秒后重连')
    reneectionTimer = setTimeout(() => {
      cfun(Ws.create(this.wsUrl))
    }, 5000)
  }

  /**
   * 启动心跳机制
   */
  startHeartbeat () {
    heartbeatTimer = setInterval(() => {
      this.send(JSON.stringify({
        mode: 'heartbeat',
        msg: 'heartbeat message'
      }))
    }, 30000)
  }
}

代码中可以看到, 当触发open 时候就开始启动心跳,当连接关闭时,停止心跳,进行重连, 连接成功会再次启动心跳,在handleMessage 函数中我们判断在mode 为heartbeat 的消息不做处理,为MESSAGE的才做处理。

服务端代码:

const WebSocket = require('ws')

const server = new WebSocket.Server({ port: '8080'})

server.on('connection', handleConnection)

function handleConnection(ws) {
  ws.on('open', handleOpen)
  ws.on('close', handleClose)
  ws.on('message', handleMessage)
  ws.on('error', handleError)
}

function handleOpen() {
  this.send(JSON.stringify({
    mode: 'MESSAGE',
    msg: '链接成功'
  }))
}

function handleClose() {
  this.send(JSON.stringify({
    mode: 'MESSAGE',
    msg: '关闭链接'
  }))
}

function handleMessage(msg) {
  const newMsg = JSON.parse(msg.toString())
  switch(newMsg.mode) {
    case 'MESSAGE':
      this.send(JSON.stringify({
        mode: 'MESSAGE',
        msg: '成功收到消息了!!!',
        content: newMsg.content
      }))
      break;
    case 'heartbeat':
      this.send(JSON.stringify({
        mode: 'heartbeat',
        msg: 'heartbeat ----'
      }))
  }
  
}

function handleError() {
  this.send(JSON.stringify({
    mode: 'MESSAGE',
    msg: '出现错误了'
  }))
}

服务端对消息类型进行判断,分别回复消息。

来看前端组件内的使用:

<script setup>
import { ref } from 'vue'
import Ws from './utils/ws.js'

let ws = null

const init = () => {
  ws = Ws.create('http://localhost:8080', function updateWs (newWs) {
    ws = newWs
  })
}
init()
const msg = ref('')
const successMsg = ref([])
const sendMsg = () => {
  if (ws) {
    ws.sendMessage(JSON.stringify({
      mode: 'MESSAGE',
      content: msg.value
    }), function (newMsg) {
      msg.value = ''
      successMsg.value.push(newMsg.content)
    })
  }
}


const closeWs = () => {
  ws.closeWs()
}
</script>

<template>
  <div>
    <p v-for="(item, index) in successMsg" :key="index">{{ item }}</p>
    <input v-model="msg" placeholder="请输入消息" />
    <button @click="sendMsg">发送</button>
    <button @click="closeWs">关闭链接</button>
  </div>
</template>

<style scoped>
p {
  padding: 0;
  margin: 0;
}

input {
  line-height: 40px;
  border: #e5e5e5 solid 1px;
  border-radius: 5px;
  margin-top: 30px;
}

button{
  border-radius: 5px;
  background: #e5e5e5;
  line-height: 40px;
  border: none;
  margin: 0 10px;
  padding: 0 16px;
}
</style>

来看看结果:

image.png

可以看到过30秒左右就会有一条心跳消息。

这时我们点击下关闭连接:

image.png

就会看到连接关闭的输出,5秒后重连,大概后5s 后就看到触发了open , 说明重连成功了。

我们来发送几条消息:

image.png

消息正常发送。

WebSocket 就分享到这里了,感谢收看,一起学习一起进步。迎接美好的明天。