Websocket实践

202 阅读3分钟

最近在项目中需要用到 WebSocket 。具体需求是, 有学生在跑圈。当他跑前,他身上的手表会给在起点的设备发消息。当他跑完后,他身上的手表会给在终点的设备发消息。最后在他跑完一圈后,后端接收到设备的数据,需要统计分析 给前端给一条数据。前端根据推的数据在大屏上显示出来。

一、简陋版

一开始写的比较简陋: 只是个 建立连接,发送数据的 过程。

贴下代码:

export default {
  data () {
    return {
      socketExample: null,
      sendArgs: {}
    }
  },
  methods: {
    // 1. 第一步 建立连接
    initSocket: function (path = 'wss://zhcctest.sunfitlink.com/dcqWebSocket', args) {
      if (typeof (WebSocket) === 'undefined') {
        alert('您的浏览器不支持socketExample')
      } else {
        if (this.socketExample) {
          this.sendArgs = args
          this.send(JSON.stringify(this.sendArgs))
          return
        }
        this.sendArgs = args
        // 实例化socketExample
        this.socketExample = new WebSocket(path)
        // 监听socketExample连接
        this.socketExample.onopen = this.socketExampleOpen
        // 监听socketExample错误信息
        this.socketExample.onerror = this.socketExampleError
        // 监听socketExample消息
        this.socketExample.onmessage = this.socketExampleGetMessage
        this.socketExample.onclose = this.socketExampleClose
      }
    },
    
    // 2. 第二步 连接成功后开始发送数据
    socketExampleOpen: function () {
      this.send(JSON.stringify(this.sendArgs))
    },
    socketExampleError: function () {
      console.group('连接错误')
    },
    socketExampleGetMessage: function (event) {},
    send: function (params) {
      this.socketExample.send(params)
    },
    socketExampleClose: function () {
      console.group('socketExample已经关闭')
    }
  },
    
  
  destroyed () {
    // 销毁监听
    this.socketExample && this.socketExample.close()
    this.socketExample = null
  }
}

采用的是 vue 的 mixins 的方式。

二、 完整版

在测试的时候遇到了一个比较严重的问题。就是当连接上的时候,不知道是不是网络原因,总是会断开连接。

后来新增了心跳机制(每隔10秒前端给后端发送一条心跳数据,后端返回一条心跳数据)。

但是还是会有时候会断开连接。

于是,又新增了 心跳检测、断开重新连接 功能。

至此,项目运行成功!

下面看完整代码:

export const WSMixins = {
  data () {
    return {
      // websocket 实例, 理论上是唯一的
      WS_socketExample: null,

      // 参数
      WS_path: '',
      WS_args: '',
      WS_isOpenHeartbeat: false,
	  WS_getMsgCallBack: null,
      // 心跳机制的 timer
      WS_heartbeatTimer: null,
      // 心跳监测的 timer
      WS_heartCheckTimer: null,
      // 重连 锁
      WS_lockReconnect: false,
      // 监测心跳是否响应的 timer
      WS_checkIsResTimer: null,
      WS_status: ''
    }
  },

  methods: {
    /**
	 * 初始化
	 * @param {String} path 路径
	 * @param {Object} args 参数
	 * @param {Boolean} isOpenHeartbeat 是否开启心跳机制
	 * @param {Function} getMsgCallBack 消息接受到后 触发的外部函数
	 * @returns
	 */
    WS_initSocket ({
      path,
      args,
      isOpenHeartbeat = false,
      getMsgCallBack
    }) {
      if (typeof WebSocket === 'undefined') {
        alert('您的浏览器不支持WebSocket')
      } else {
        if (this.WS_socketExample) {
          this.WS_reset()
        }

        // 记录参数
        this.WS_path = path
        this.WS_args = args
        this.WS_isOpenHeartbeat = isOpenHeartbeat
        this.WS_getMsgCallBack = getMsgCallBack

        this.WS_socketExample = new WebSocket(path)

        // 实例化后,进行监听
        this.WS_socketExample.onopen = this.WS_socketExampleOpen
        this.WS_socketExample.onerror = this.WS_socketExampleError
        this.WS_socketExample.onmessage = this.WS_socketExampleGetMessage
        this.WS_socketExample.onclose = this.WS_socketExampleClose
      }
    },

    WS_reset () {
      this.WS_socketExample = null
      this.WS_path = ''
      this.WS_args = ''
      this.WS_isOpenHeartbeat = false
      clearTimeout(this.WS_heartbeatTimer)
      this.WS_heartbeatTimer = null

      clearInterval(this.WS_heartCheckTimer)
      this.WS_heartCheckTimer = null

      clearTimeout(this.WS_checkIsResTimer)
      this.WS_checkIsResTimer = null

      clearTimeout(this.WS_reconnectTimer)
      this.WS_reconnectTimer = null
    },

    // 连接建立时触发
    WS_socketExampleOpen () {
      // 开启心跳机制
      if (this.WS_isOpenHeartbeat) {
        this.WS_startHeartbeat()
      }

      if (this.WS_args) {
        this.WS_socketExample.send(JSON.stringify(this.WS_args))
      }
    },

    // 通信发生错误时触发
    WS_socketExampleError (error) {
      console.log('遇到错误了:', error)
      this.WS_reconnect()
    },

    // 	客户端接受服务端数据时触发
    WS_socketExampleGetMessage (res) {
      // 如果接受到信息, 则重新开始下一轮心跳机制
      if (this.WS_isOpenHeartbeat) {
        this.WS_startHeartbeat()
      }

	  this.WS_getMsgCallBack && this.WS_getMsgCallBack(res)
    },

    // 连接关闭时触发
    WS_socketExampleClose () {
      // 如果不是主动关闭
      if (this.WS_status !== 'close') {
        this.WS_reconnect()
      }
    },

    // 开始心跳机制
    WS_startHeartbeat () {
      clearTimeout(this.WS_heartbeatTimer)
      this.WS_heartbeatTimer = null
      clearTimeout(this.WS_checkIsResTimer)
      this.WS_checkIsResTimer = null

      this.WS_heartbeatTimer = setTimeout(() => {
        if (this.WS_socketExample.readyState === 1) {
          this.WS_socketExample.send(JSON.stringify({
            // 后端要求心跳的时候也需要传递参数过去
            ...this.WS_args,
            startHeartbeat: true
          }))

          // 心跳发送后,如果服务器未响应则断开,如果响应了会被重置下面的定时器
          this.WS_checkIsResTimer = setTimeout(() => {
            this.WS_socketExample.close()
          }, 1000 * 10)
        } else {
          this.WS_reconnect()
        }
      }, 1000 * 10)
    },

    // 重新连接
    WS_reconnect () {
      if (this.WS_socketExample.readyState === 1) {
        // 如果状态等于1导表 ws 连接正常
        return
      }

      if (this.WS_lockReconnect) {
        return
      }
      this.WS_lockReconnect = true

      this.WS_reconnectTimer = setTimeout(() => {
        console.log('尝试重连...')
        this.WS_initSocket({
          path: this.WS_path,
          args: this.WS_args,
          isOpenHeartbeat: this.WS_isOpenHeartbeat,
          getMsgCallBack: this.WS_getMsgCallBack
        })
        this.WS_lockReconnect = false

        clearTimeout(this.WS_reconnectTimer)
        this.WS_reconnectTimer = null
      }, 5000)
    },

    // 心跳监测
    WS_heartCheck () {
      this.WS_heartCheckTimer = setInterval(() => {
        this.WS_reconnect()
      }, 1500)
    }
  },

  destroyed () {
    this.WS_status = 'close'
    this.WS_reset()
  }

}


依旧是采用 mixins 的方式,下面是在页面中调用的代码:

import { WSMixins } from '@/utils/socketMixins.js'

export default {
  mixins: [WSMixins],
  created () {
    startSocket()
 },
  startSocket () {
      this.WS_initSocket({
        path: `wss://socket路径`,
        args: {参数 },
        isOpenHeartbeat: true, // 是否开启心跳
        getMsgCallBack: ({ data }) => {
          // 接收到消息的回调
          // 对于我的业务来说,这里就是显示大屏页面
      })
    },