当前位置: 首页 > article >正文

WebSocket实现直播弹幕滚动推送效果

WebSocket + 弹幕滚动推送

      • WebSocket 通信协议优点
      • 实现过程详细解析
        • 1. 初始化 WebSocket 连接
        • 2. WebSocket 事件回调
        • 2.2 连接错误 (onerror)
        • 2.3 接收到消息 (onmessage)
        • 2.4 连接关闭 (onclose)
        • 3. 心跳检测机制
        • 4. WebSocket 重新连接机制
        • 5. 滚动加载和历史数据
      • 总结
      • 代码示例

WebSocket 通信协议优点

WebSocket 是一种通信协议,它提供了一个全双工、低延迟的通信通道,允许客户端和服务器之间进行实时的数据交换。它比传统的轮询请求方式更加高效,因为它建立在持久连接之上,而不需要每次都重新建立连接。使用 WebSocket,可以实现实时数据推送、在线聊天、消息通知等功能。

双向通信: WebSocket 允许客户端和服务器之间进行双向通信,这意味着双方都可以主动发送消息,而不仅仅是客户端向服务器发送请求。
降低延迟: 相较于传统的 HTTP 协议,WebSocket 在连接建立后能保持一个持久连接,从而减少了每次请求和响应之间的延迟。
节省带宽: WebSocket 使用一种轻量级的帧格式,相比于 HTTP,相同的数据传输量能够消耗更少的带宽和资源。
实时性: WebSocket 特别适合需要实时数据更新的应用,比如在线游戏、即时聊天应用、实时股票更新或体育赛事直播。
简单的 API: WebSocket 提供了易用的 JavaScript API,使得开发人员能够轻松实现复杂的实时通信功能。

适用场景:

  • 在线游戏:需要快速、安全、实时的数据传输。
  • 聊天应用:实现实时的消息推送。
  • 股票市场:实时更新数据的应用程序。
  • 协作工具:如在线文档编辑,允许多用户实时便捷地进行协作。

实现过程详细解析

详细解析在 Vue 代码中 WebSocket实现直播弹幕滚动效果过程,逐步说明各个方法和关键部分。
在这里插入图片描述

1. 初始化 WebSocket 连接
setupWebSocket() {
  const env = process.env.VUE_APP_ENV
  const urls = {
    prod: 'wss://domain.name/net/websocket/',
    test: 'wss://domaintest.name/net/websocket/',
    dev: 'ws://127.0.0.0:8080/net/websocket/'
  }
  const wsUrl = urls[env] || urls.dev
  if ('WebSocket' in window) {
    this.websocket = new WebSocket(`${wsUrl}${this.deptId}`)
    this.websocket.onopen = this.handleWebSocketOpen
    this.websocket.onerror = this.handleWebSocketError
    this.websocket.onmessage = this.handleWebSocketMessage
    this.websocket.onclose = this.handleWebSocketClose
  } else {
    alert('WebSocket not supported')
    this.reconnectWebSocket()
  }
}

环境配置: 通过 process.env.VUE_APP_ENV 获取当前应用的环境,并为不同环境(生产、测试、开发)指定 WebSocket 服务的 URL。

  • wss:// 是 WebSocket 的安全协议,类似于 HTTPS。
  • ws:// 是非加密的 WebSocket协议,通常在开发时使用。

WebSocket 实例化: 通过 new WebSocket(url) 创建 WebSocket 实例,连接到指定的 URL。URL 中动态传入 deptId(部门ID)来建立连接。

事件监听: 设置了 4 个 WebSocket 事件回调:

  • onopen:连接成功后触发,调用 handleWebSocketOpen。
  • onerror:连接出错时触发,调用 handleWebSocketError。
  • onmessage:接收到消息时触发,调用 handleWebSocketMessage。
  • onclose:连接关闭时触发,调用 handleWebSocketClose。

浏览器兼容性检查: 通过 ‘WebSocket’ in window 判断当前浏览器是否支持 WebSocket。如果不支持,则弹出提示并尝试重连。

2. WebSocket 事件回调

连接成功 (onopen)

handleWebSocketOpen() {
  this.heartCheck.start()
  console.log('WebSocket connection established.')
}

  • 当 WebSocket 连接成功时,调用 handleWebSocketOpen。在这里,调用了 heartCheck.start()
    来启动心跳检测,保持连接的持续性。
  • 控制台打印 WebSocket connection established. 来表明连接成功。
2.2 连接错误 (onerror)
handleWebSocketError(error) {
  if (!this.beforeDestroyLeave) this.reconnectWebSocket()
  console.error('WebSocket error:', error)
}

  • 当 WebSocket 连接发生错误时,调用 handleWebSocketError。此时,尝试重新连接 WebSocket (this.reconnectWebSocket())。并在控制台输出错误信息。
2.3 接收到消息 (onmessage)
handleWebSocketMessage(event) {
  this.heartCheck.reset().start()
  const data = JSON.parse(event.data)
  if (data.code === '心跳检测成功') return
  this.websocketData.unshift(data)
  this.updateHistoryData()
}

  • 当 WebSocket 收到消息时,调用 handleWebSocketMessage。
  • 通过 this.heartCheck.reset().start() 重置并重新启动心跳检测。
  • 将服务器返回的数据 event.data 解析成 JSON 格式(假设返回的消息是 JSON 字符串)。
  • 如果接收到的消息是“心跳检测成功”的响应,则跳过进一步处理。
  • 将接收到的数据 data 插入到 this.websocketData 数组的开头,并调用 updateHistoryData()
    更新历史数据展示。
2.4 连接关闭 (onclose)
handleWebSocketClose() {
  if (!this.beforeDestroyLeave) this.reconnectWebSocket()
  console.log('WebSocket connection closed.')
}

  • 当 WebSocket 连接关闭时,调用 handleWebSocketClose。如果页面没有销毁,则尝试重新连接 WebSocket。
  • 控制台打印 WebSocket connection closed. 表示连接已关闭。
3. 心跳检测机制

心跳检测的作用是定期检查 WebSocket 连接是否正常。如果连接断开,自动重连。为了避免服务端超时断开连接,客户端定期发送 ping 消息到服务器,以保持连接的活跃。

createHeartCheck() {
  return {
    timeout: 55000,
    timeoutObj: null,
    serverTimeoutObj: null,
    reset() {
      clearTimeout(this.timeoutObj)
      clearTimeout(this.serverTimeoutObj)
    },
    start() {
      this.reset()
      this.timeoutObj = setTimeout(() => {
        this.websocket.send('ping:websocket') // 向服务器发送心跳数据
        this.serverTimeoutObj = setTimeout(() => {
          this.websocket.close() // 如果超过指定时间没有响应,关闭 WebSocket 连接
        }, this.timeout)
      }, this.timeout)
    }
  }
}

心跳检测机制:

  • timeout 设置为 55000 毫秒(即 55 秒)。这是发送心跳的时间间隔。
  • timeoutObj 和 serverTimeoutObj 分别用于存储客户端和服务器的超时计时器。
  • reset() 方法用来清除现有的超时计时器。
  • start() 方法用来启动心跳检测:
    • 每隔 timeout 毫秒发送一条心跳消息 ping:websocket 给服务器。
    • 启动一个定时器,如果在 timeout 毫秒内没有收到响应,则认为连接断开,关闭 WebSocket。
4. WebSocket 重新连接机制

当 WebSocket 连接断开或出错时,尝试自动重连。防止重复连接导致多次 WebSocket 实例被创建。

reconnectWebSocket() {
  if (this.lockReconnect) return // 如果正在重连,直接返回,避免重复连接
  this.lockReconnect = true
  setTimeout(() => {
    this.setupWebSocket() // 尝试重新建立 WebSocket 连接
    this.lockReconnect = false
  }, 5000) // 每隔 5 秒重连一次
}

  • 使用 lockReconnect 变量来防止多次重连请求,确保每次只会有一个重连请求在进行。
  • 重连间隔为 5 秒。
5. 滚动加载和历史数据
updateHistoryData() {
  if (this.websocketData.length > 5) {
    this.historyList.push(this.websocketData[4]) // 将第五条数据推入历史记录
    this.websocketData.splice(5, 1) // 删除超过五条的数据
  }
  if (!this.isBindScrolled) {
    this.$nextTick(() => {
      const msg = document.querySelector('.list-box')
      msg.scrollTop = msg.scrollHeight // 滚动到底部
    })
  }
  this.restNums = this.isBindScrolled ? ++this.restNums : 0
  this.restComment = this.restNums >= 99 ? '99+' : this.restNums
}

  • 当 WebSocket 收到新消息时,调用 updateHistoryData() 更新消息展示。
  • 新消息插入到 this.websocketData 数组的前端,如果数据超过 5 条,则将第 5 条消息移动到历史记录
    this.historyList 中,并删除多余的消息。

总结

1. WebSocket 实现过程:

  • 在 setupWebSocket() 中通过 new WebSocket(url) 实例化WebSocket,设置连接、错误、消息、关闭等回调。
  • 使用心跳机制保持 WebSocket 连接的活跃,避免连接超时。
  • 当连接断开时,会自动进行重连,确保客户端始终保持连接状态。

2. WebSocket 心跳机制:

  • 通过定时发送 ping 消息来检测连接状态,如果超过设定时间没有收到响应,则关闭连接并进行重连。

3. 消息处理:

  • 每当接收到服务器推送的消息时,首先处理心跳消息,更新历史数据并控制滚动行为。

4.滚动加载机制:

  • 通过监听滚动条事件,动态加载更多历史记录。

这样,整个 WebSocket 的实现不仅保证了消息的实时推送,还通过心跳机制和重连机制增强了连接的稳定性。

代码示例

<script>
import { mapState } from 'vuex'
import { getHistory } from '@/api/user'

export default {
  data() {
    return {
      time: '',
      week: '',
      date: '',
      deptId: '',
      websocket: null,
      heartCheck: null,
      lockReconnect: false,
      beforeDestroyLeave: false, // 是否离开页面
      websocketData: [],
      historyList: [],
      page: 1,
      pageSize: 10,
      dataLoading: false,
      finished: false,
      isBindScrolled: false,
      restNums: 0,
      restComment: 0,
      count: 0,
      winWidth: null,
      winHeight: null
    }
  },
  computed: {
    ...mapState('user', ['userInfo', 'vwSOaDeptInfo'])
  },
  created() {
    this.deptId = this.vwSOaDeptInfo.deptId
    this.heartCheck = this.createHeartCheck()
  },
  mounted() {
    this.setupWebSocket()
    this.setupWindowResizeListener()
    this.getCurrentTime()
  },
  beforeDestroy() {
    this.cleanupWebSocket()
  },
  filters: {
    recognitionType(v) {
      const type = {
        1: '员工',
        2: '访客',
        3: '重点人员',
        4: '陌生人',
        5: '未识别',
        6: 'VIP访客'
      }
      return type[v]
    }
  },
  methods: {
    setupWebSocket() {
      const env = process.env.VUE_APP_ENV
      const urls = {
        prod: 'wss://domain.name/net/websocket/',
        test: 'wss://domaintest.name/net/websocket/',
        dev: 'ws://127.0.0.0:8080/net/websocket/'
      }
      const wsUrl = urls[env] || urls.dev
      if ('WebSocket' in window) {
        this.websocket = new WebSocket(`${wsUrl}${this.deptId}`)
        this.websocket.onopen = this.handleWebSocketOpen
        this.websocket.onerror = this.handleWebSocketError
        this.websocket.onmessage = this.handleWebSocketMessage
        this.websocket.onclose = this.handleWebSocketClose
      } else {
        alert('WebSocket not supported')
        this.reconnectWebSocket()
      }
    },
    handleWebSocketOpen() {
      this.heartCheck.start()
      console.log('WebSocket connection established.')
    },
    handleWebSocketError(error) {
      if (!this.beforeDestroyLeave) this.reconnectWebSocket()
      console.error('WebSocket error:', error)
    },
    handleWebSocketMessage(event) {
      this.heartCheck.reset().start()
      const data = JSON.parse(event.data)
      if (data.code === '心跳检测成功') return
      this.websocketData.unshift(data)
      this.updateHistoryData()
    },
    handleWebSocketClose() {
      if (!this.beforeDestroyLeave) this.reconnectWebSocket()
      console.log('WebSocket connection closed.')
    },
    reconnectWebSocket() {
      if (this.lockReconnect) return
      this.lockReconnect = true
      setTimeout(() => {
        this.setupWebSocket()
        this.lockReconnect = false
      }, 5000)
    },
    createHeartCheck() {
      return {
        timeout: 55000,
        timeoutObj: null,
        serverTimeoutObj: null,
        reset() {
          clearTimeout(this.timeoutObj)
          clearTimeout(this.serverTimeoutObj)
        },
        start() {
          this.reset()
          this.timeoutObj = setTimeout(() => {
            this.websocket.send('ping:websocket')
            this.serverTimeoutObj = setTimeout(() => {
              this.websocket.close()
            }, this.timeout)
          }, this.timeout)
        }
      }
    },
    cleanupWebSocket() {
      this.beforeDestroyLeave = true
      this.heartCheck.reset()
      this.websocket.close()
    },
    setupWindowResizeListener() {
      window.addEventListener('resize', this.updateWindowDimensions)
      this.updateWindowDimensions()
    },
    updateWindowDimensions() {
      this.winWidth = window.innerWidth || document.documentElement.clientWidth
      this.winHeight = window.innerHeight || document.documentElement.clientHeight
    },
    getCurrentTime() {
      setInterval(this.updateTime, 1000)
    },
    updateTime() {
      const now = new Date()
      const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
      this.week = weekDays[now.getDay()]
      this.date = `${now.getMonth() + 1}${now.getDate()}`
      this.time = `${now.getHours()}:${now.getMinutes().toString().padStart(2, '0')}`
    },
    queryHistory() {
      getHistory({ pageNumReq: this.page, pageSizeReq: this.pageSize, deptId: this.deptId })
        .then(res => {
          if (res.code === 0) {
            this.historyList = [...res.t.list.reverse(), ...this.historyList]
            this.page++
            this.dataLoading = false
            this.finished = this.historyList.length >= res.t.total || res.t.list.length === 0
            this.scrollToBottomIfNeeded()
          }
        })
        .catch(error => {
          this.dataLoading = false
          console.error('Error fetching history:', error)
        })
    },
    addData() {
      const newData = {
        createTime: '2022-03-09 14:05:32',
        personName: `${this.count}`,
        recognitionType: 2,
        signType: '签到成功',
        visitType: 2
      }
      this.websocketData.unshift(newData)
      this.updateHistoryData()
    },
    updateHistoryData() {
      if (this.websocketData.length > 5) {
        this.historyList.push(this.websocketData[4])
        this.websocketData.splice(5, 1)
      }
      if (!this.isBindScrolled) {
        this.$nextTick(() => {
          const msg = document.querySelector('.list-box')
          msg.scrollTop = msg.scrollHeight
        })
      }
      this.restNums = this.isBindScrolled ? ++this.restNums : 0
      this.restComment = this.restNums >= 99 ? '99+' : this.restNums
    },
    scrool(e) {
      const ele = e.target
      if (!this.isBindScrolled && ele.scrollTop < 6) {
        this.isBindScrolled = true
        if (!this.finished) {
          this.queryHistory()
        }
      }
    },
    scrollToBottomIfNeeded() {
      if (!this.isBindScrolled) {
        this.$nextTick(() => {
          const msg = document.querySelector('.list-box')
          msg.scrollTop = msg.scrollHeight
        })
      }
    },
    back() {
      this.$router.replace('/home')
    }
  }
}
</script>


http://www.kler.cn/a/455935.html

相关文章:

  • 从0入门自主空中机器人-2-1【无人机硬件框架】
  • PaddleOCR文字识别模型的FineTune
  • 音视频学习(二十八):websocket-flv
  • 使用Excel制作通达信自定义外部数据,安排!!!
  • flask后端开发(2):URL与视图
  • SpringBoot配置文件、热部署、YAML语法、配置文件值注入
  • 【环境配置】Jupyter Notebook切换虚拟环境
  • Html——10 关键字和描述
  • CSS基础入门【2】
  • Python爬虫(一)- Requests 安装与基本使用教程
  • [Android]init中添加新的command
  • 高中数学刷题版:函数奇偶性[干货]
  • GaussDB典型SQL调优点之自诊断和语句下推调优
  • 五模型对比!Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量时间序列预测
  • Kafka_2.13-3.6.0 常用命令快速指南
  • .net core 的软件开发流程
  • Springboot关于格式化记录
  • java常用类(下)
  • uniapp使用live-pusher实现模拟人脸识别效果
  • 窗口函数-详细讲解分析
  • vue 集成 webrtc-streamer 播放视频流 - 解决阿里云内外网访问视频流问题
  • 面试知识点汇总_02
  • 【STM32】RTT-Studio中HAL库开发教程十:EC800M-4G模块使用
  • 记录使用uim4的自定义头部
  • 黑马点评——基于Redis
  • Java全栈项目-校园公告管理系统开发实践