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

news2025/3/13 14:51:53

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.coloradmin.cn/o/2267269.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】球弹跳高度的计算:思路分析与优化

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;题目描述输入格式输出格式输入输出示例 &#x1f4af;两种代码实现及其对比我的代码实现代码分析优点与不足 老师的代码实现代码分析优点与不足 &#x1f4af;两种实现的对…

ASP.NET Core Web API Hangfire

ASP.NET Core Web API Hangfire 前言一、安装二、相关代码1.代码片段2.代码片段3.运行效果 三、测试代码1.即发即弃作业2.延迟作业3.重复作业4.延续作业5.页面调度作业 前言 &#x1f468;‍&#x1f4bb;&#x1f468;‍&#x1f33e;&#x1f4dd;记录学习成果&#xff0c;以…

智慧园区小程序开发制作功能介绍

智慧园区小程序开发制作功能介绍 智慧园区小程序系统作为一款面向园区企业的一站式线上服务平台&#xff0c;可为企业提供数智化的园区办公服务。智慧园区小程序功能介绍 1、园区公告、政策信息查看足不出户掌握最新动态&#xff0c;“园区公告、政策信息”等信息。首页点击对应…

Three.js Journey (notes)

Ref Three.js中文网 Three.js Journey — Learn WebGL with Three.js Part 1 first-threejs-project 1. build tools ①vite vs webpack do a bunch of things like optimizations,cache breaking,source mapping,running a local server,etc. build tool vite (most appr…

【玩转OCR】 | 腾讯云智能结构化OCR在多场景的实际应用与体验

文章目录 引言产品简介产品功能产品优势 API调用与场景实践图像增强API调用实例发票API调用实例其他场景 结语相关链接 引言 在数字化信息处理的时代&#xff0c;如何高效、精准地提取和结构化各类文档数据成为了企业和政府部门的重要需求。尤其是在面对海量票据、证件、表单和…

c# VS2022安装教程

换了部电脑&#xff0c;重新安装vs2022&#xff0c;做个记录给自己以后方便看 官网下载地址&#xff1a;下载 Visual Studio Tools - 免费安装 Windows、Mac、Linux 官网下载vs2022社区版&#xff0c;安装包 双击后点击继续&#xff0c;需要全程联网 随后出现这个界面并勾选…

Bluetooth Spec【0】蓝牙核心架构

蓝牙核心系统由一个主机、一个主控制器和零个或多个辅助控制器组成蓝牙BR/ EDR核心系统的最小实现包括了由蓝牙规范定义的四个最低层和相关协议&#xff0c;以及一个公共服务层协议&#xff1b;服务发现协议&#xff08;SDP&#xff09;和总体配置文件要求在通用访问配置文件&a…

PCA降维MATLAB代码解释及应用场景

代码整体功能概述 这段代码主要实现了以下几个功能&#xff1a;首先读取两个 CSV 文件中的数据&#xff0c;对数据进行归一化处理后合并&#xff0c;接着绘制原始数据的散点图进行可视化展示&#xff0c;然后应用主成分分析&#xff08;PCA&#xff09;算法对合并后的数据进行…

深入理解Redis

1.数据结构类型 数据结构-SDS-简单动态字符串 Redis构建了一种新字符串结构,称为简单动态字符串(Simple Dynamic String),简称SDS。 Redis未直接使用C语言的字符串,如:char* s = "hello",本质是字符数组: {h, e, l, l, o, \0}。因为C语言字符串存在很多问题…

数字后端培训项目Floorplan常见问题系列专题续集1

今天继续给大家分享下数字IC后端设计实现floorplan阶段常见问题系列专题。这些问题都是来自于咱们社区IC后端训练营学员提问的问题库。目前这部分问题库已经积累了4年了&#xff0c;后面会陆续分享这方面的问题。 希望对大家的数字后端学习和工作有所帮助。 数字后端项目Floor…

【TaskBasics】- KRTS C++示例精讲(3)

TaskBasics示例讲解 目录 TaskBasics示例讲解结构说明 项目打开请查看【BaseFunction精讲】。 结构说明 TaskBasics&#xff1a;应用层程序&#xff0c;主要用于人机交互、数据显示、内核层数据交互等&#xff1b; TaskBasics.h &#xff1a; 数据定义TaskBasics.cpp&#xff…

jenkins集成工具(一)部署php项目

目录 什么是CI 、CD Jenkins集成工具 一、Jenkins介绍 二、jenkins的安装和部署 环境部署 安装jenkins 安装gitlab 配置镜像源进行安装 修改密码 安装git工具 上传测试代码 Jenkins部署php项目wordpress 发布php代码 安装插件 测试代码发布 实现发布成功发送邮件…

在交叉编译中,常见的ELF(elf)到底是什么意思?

ELF 是 Executable and Linkable Format 的缩写&#xff0c;中文翻译为“可执行与可链接格式”。它是一种通用的文件格式&#xff0c;主要用于存储可执行文件、目标文件&#xff08;编译后的中间文件&#xff09;、动态库&#xff08;.so 文件&#xff09;以及内存转储文件&…

图神经网络_图嵌入_Struc2Vec

0 背景 之前的node embedding方式&#xff0c;都是基于近邻关系&#xff0c;但是有些节点没有近邻&#xff0c;也有结构相似性。如图中的u、v节点。 struc2vec算法适用于捕获结构相似性。 1 相似度&#xff08;距离&#xff09;计算 1.1 公式 f k ( u , v ) f k − 1 ( u …

Linux内核中Typec CC检测原理及主从模式切换原理

一&#xff0c;Typec CC引脚定义 可以看到&#xff0c;数据传输主要有TX/RX两组差分信号&#xff0c;CC1和CC2是两个关键引脚&#xff0c;作用很多&#xff1a; • 探测连接&#xff0c;区分正反面&#xff0c;区分DFP和UFP&#xff0c;也就是主从 • 配置Vbus&#xff0c;有US…

uniapp通过v-if进行判断时,会出现闪屏?【已解决】

1.问题&#xff1a;按钮切换时&#xff0c;通过v-if来判断&#xff0c;会出现闪烁情况&#xff0c;影响用户体验 2.v-if 闪烁问题可能的原因 ‌条件切换频繁‌&#xff1a;如果 v-if 指令的条件在短时间内频繁切换&#xff0c;会导致元素不断被销毁和重新创建&#xff0c;从而…

webrtc获取IceCandidate流程

在WebRTC(Web Real-Time Communication)中,ICECandidate是一个关键概念,它用于描述在建立点对点(P2P)连接时可以考虑的潜在通信端点。以下是关于WebRTC中ICECandidate的详细解释: 一、ICECandidate的定义 ICECandidate对象通常包含以下关键属性: foundation:用于唯一…

计算机网络习题(第1章 概论 第2章 数据通信基础)

第1章 概论 1、计算机网络 2、互联网 3、计算机网络体系结构 分层模型 OSI/RM 7层模型 TCP/IP 5层模型 协议、PDU、SDU、SAP等术语 数据封装&#xff08;计算&#xff09; 第2章 数据通信基础 1、数据通信系统组成 2、主要性能指标 数据传输速率 码元速率 时延 3…

Springboot项目下面使用Vue3 + ElementPlus搭建侧边栏首页

Springboot项目下面、在html 页面 Vue3 ElementPlus 搭建侧边栏首页 1、效果图 2、static 文件下面的项目结构 3、代码实现 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>首页</title><…

【C++11】可变参数模版

目录 一、可变参数模版 1.1 基本语法及原理 1.2 包拓展 1.3 empalce系列接口 一、可变参数模版 之前我们在C语言中就学过可变参数&#xff0c;但是模版类型是固定的&#xff0c;怎么变呢&#xff1f;这里c11就给出了可变参数模版 1.1 基本语法及原理 C11支持可变参数模…