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

深入浅出 WebSocket:构建实时数据大屏的高级实践

简介

请参考下方,学习入门操作

基于 Flask 和 Socket.IO 的 WebSocket 实时数据更新实现

在当今数字化时代,实时性是衡量互联网应用的重要指标之一。无论是股票交易、在线游戏,还是实时监控大屏,WebSocket 已成为实现高效、双向实时通信的最佳选择之一。本文将通过一个基于 WebSocket 实现的实时数据大屏案例,深入探讨 WebSocket 的高级用法和优化技巧。

WebSocket 的典型应用场景

  • 实时数据监控:如运营监控大屏、设备状态监控等。
  • 在线协作:如 Google Docs 的多人编辑。
  • 实时聊天:如即时通讯工具。
  • 实时通知:如电商的价格变动提醒。

场景分析:实时数据监控大屏

本案例的目标是实现一个实时数据监控大屏,通过 WebSocket 技术,将实时更新的数据动态展示在用户界面中。

需求分析

  • 实现不同房间的数据订阅(如销售数据和访问数据)。
  • 支持多客户端实时接收服务器推送的最新数据。
  • 动态更新界面,提供流畅的用户体验。

技术选型

  • 前端:HTML、CSS、JavaScript 使用 Socket.IO 客户端库。
  • 后端:基于 Flask 和 Flask-SocketIO 实现 WebSocket 服务。
  • 实时数据生成:使用 Python 的 random 模块模拟实时数据。

后端实现

from flask import Flask, render_template, request
from flask_socketio import SocketIO, emit, join_room, leave_room
import random
import time
from threading import Threadapp = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)# 存储客户端订阅的房间信息
client_rooms = {}
# 存储数据生成器线程
data_threads = {}def generate_sales_data(room):"""生成销售相关数据"""while room in data_threads and data_threads[room]['active']:data = {'sales': random.randint(1000, 5000),'orders': random.randint(50, 200),'timestamp': time.strftime('%H:%M:%S')}socketio.emit('update_data', data, room=room)time.sleep(2)def generate_visitor_data(room):"""生成访问量相关数据"""while room in data_threads and data_threads[room]['active']:data = {'visitors': random.randint(100, 1000),'active_users': random.randint(50, 300),'timestamp': time.strftime('%H:%M:%S')}socketio.emit('update_data', data, room=room)time.sleep(3)@app.route('/')
def index():return render_template('index.html')@socketio.on('join')
def on_join(data):"""处理客户端加入房间请求"""room = data.get('room')if not room:return# 获取客户端IDclient_id = request.sid# 将客户端加入房间join_room(room)client_rooms[client_id] = roomprint(f'Client {client_id} joined room: {room}')# 如果房间没有数据生成器线程,创建一个if room not in data_threads:data_threads[room] = {'active': True,'thread': Thread(target=generate_sales_data if room == 'sales' else generate_visitor_data,args=(room,),daemon=True)}data_threads[room]['thread'].start()@socketio.on('leave')
def on_leave(data):"""处理客户端离开房间请求"""room = data.get('room')if not room:returnclient_id = request.sidleave_room(room)if client_id in client_rooms:del client_rooms[client_id]print(f'Client {client_id} left room: {room}')@socketio.on('connect')
def handle_connect():print(f'Client connected: {request.sid}')@socketio.on('disconnect')
def handle_disconnect():client_id = request.sidif client_id in client_rooms:room = client_rooms[client_id]leave_room(room)del client_rooms[client_id]# 检查房间是否还有其他客户端if not client_rooms.values().__contains__(room):# 如果没有,停止数据生成器if room in data_threads:data_threads[room]['active'] = Falsedata_threads[room]['thread'].join(timeout=1)del data_threads[room]print(f'Client disconnected: {client_id}')if __name__ == '__main__':socketio.run(app, debug=True, host='0.0.0.0', port=5000)

数据生成与推送

后端的核心逻辑是数据生成与推送:

  • 数据生成:通过 generate_sales_datagenerate_visitor_data 函数生成随机数据,并定时推送到客户端。
  • 房间管理:通过 join_roomleave_room 方法管理客户端的房间订阅。
  • 线程管理:使用线程来生成数据,并在客户端离开房间时停止线程。

HTML 结构

<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>实时数据大屏</title><script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script><style>body {margin: 0;padding: 20px;background-color: #1a1a1a;color: #fff;font-family: Arial, sans-serif;}.controls {text-align: center;margin-bottom: 30px;}.btn {background-color: #4CAF50;border: none;color: white;padding: 10px 20px;margin: 0 10px;border-radius: 5px;cursor: pointer;transition: background-color 0.3s;}.btn:hover {background-color: #45a049;}.btn.active {background-color: #2E7D32;}.dashboard {display: grid;grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));gap: 20px;max-width: 1200px;margin: 0 auto;}.card {background-color: #2a2a2a;border-radius: 10px;padding: 20px;text-align: center;box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);}.card h2 {margin: 0 0 10px 0;color: #4CAF50;}.value {font-size: 2.5em;font-weight: bold;margin: 10px 0;}.timestamp {text-align: right;color: #888;margin-top: 20px;}@keyframes pulse {0% { transform: scale(1); }50% { transform: scale(1.05); }100% { transform: scale(1); }}.update {animation: pulse 0.5s ease-in-out;}</style>
</head>
<body><h1 style="text-align: center; margin-bottom: 40px;">实时数据监控</h1><div class="controls"><button class="btn" onclick="toggleRoom('sales')" id="salesBtn">销售数据</button><button class="btn" onclick="toggleRoom('visitors')" id="visitorsBtn">访问数据</button></div><div class="dashboard"><!-- 销售数据卡片 --><div class="card" id="salesCard" style="display: none;"><h2>销售额</h2><div id="sales" class="value">0</div><div>实时销售金额 (元)</div></div><div class="card" id="ordersCard" style="display: none;"><h2>订单数</h2><div id="orders" class="value">0</div><div>实时订单统计</div></div><!-- 访问数据卡片 --><div class="card" id="visitorsCard" style="display: none;"><h2>访问量</h2><div id="visitors" class="value">0</div><div>当前访问人数</div></div><div class="card" id="activeUsersCard" style="display: none;"><h2>活跃用户</h2><div id="active_users" class="value">0</div><div>实时活跃用户数</div></div></div><div class="timestamp" id="timestamp">最后更新时间: --:--:--</div><script>const socket = io();let currentRooms = new Set();// 更新数据的函数function updateValue(elementId, value) {const element = document.getElementById(elementId);if (element) {element.textContent = value;element.classList.remove('update');void element.offsetWidth; // 触发重绘element.classList.add('update');}}// 切换房间function toggleRoom(room) {const btn = document.getElementById(room + 'Btn');if (currentRooms.has(room)) {// 离开房间socket.emit('leave', { room: room });currentRooms.delete(room);btn.classList.remove('active');// 隐藏相关卡片if (room === 'sales') {document.getElementById('salesCard').style.display = 'none';document.getElementById('ordersCard').style.display = 'none';} else {document.getElementById('visitorsCard').style.display = 'none';document.getElementById('activeUsersCard').style.display = 'none';}} else {// 加入房间socket.emit('join', { room: room });currentRooms.add(room);btn.classList.add('active');// 显示相关卡片if (room === 'sales') {document.getElementById('salesCard').style.display = 'block';document.getElementById('ordersCard').style.display = 'block';} else {document.getElementById('visitorsCard').style.display = 'block';document.getElementById('activeUsersCard').style.display = 'block';}}}// 监听数据更新事件socket.on('update_data', function(data) {// 更新所有收到的数据Object.keys(data).forEach(key => {if (key !== 'timestamp') {updateValue(key, data[key]);}});document.getElementById('timestamp').textContent = '最后更新时间: ' + data.timestamp;});// 连接时自动加入销售数据房间socket.on('connect', function() {toggleRoom('sales');});</script>
</body>
</html>

JavaScript 逻辑

在前端代码中,我们使用了 Socket.IO 客户端库来与服务器进行 WebSocket 通信。主要逻辑如下:

  • 连接服务器:通过 io() 方法连接到服务器。
  • 切换房间:用户点击按钮时,通过 toggleRoom 函数切换不同的数据房间。
  • 更新数据:监听 update_data 事件,更新页面上的数据。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

WebSocket 的高级实践与优化

在实际应用中,可能需要管理多个房间,每个房间对应不同的数据类型或用户组。通过 join_roomleave_room 方法,可以轻松实现多房间管理。

数据压缩与优化

对于大规模数据传输,可以考虑使用数据压缩技术来减少带宽占用。例如,使用 gzipbrotli 压缩数据包,或者在前端进行数据解压缩。

断线重连与心跳机制

WebSocket 连接可能会因为网络问题而断开。为了保证连接的稳定性,可以实现断线重连机制和心跳包检测。通过定时发送心跳包,可以及时检测连接状态,并在断线时自动重连。

安全性与权限控制

在生产环境中,安全性是一个不可忽视的问题。可以通过以下方式增强 WebSocket 连接的安全性:

  • 使用 HTTPS:确保 WebSocket 连接通过加密的 HTTPS 协议进行。
  • 身份验证:在连接建立时进行身份验证,确保只有授权用户才能访问数据。
  • 权限控制:根据用户角色控制其访问的房间和数据类型。

扩展与定制

WebSocket 的应用场景非常广泛,可以根据具体需求进行扩展和定制。例如,结合 WebRTC 实现实时音视频通信,或者结合 WebGL 实现实时3D数据可视化。

相关文章:

深入浅出 WebSocket:构建实时数据大屏的高级实践

简介 请参考下方&#xff0c;学习入门操作 基于 Flask 和 Socket.IO 的 WebSocket 实时数据更新实现 在当今数字化时代&#xff0c;实时性是衡量互联网应用的重要指标之一。无论是股票交易、在线游戏&#xff0c;还是实时监控大屏&#xff0c;WebSocket 已成为实现高效、双向…...

三开关VUE组件

一、使用效果 <template><QqThreeSwitch v-model"value" /><!-- <SqThreeSwitch v-model"value" :options"[test1, test2, test3]"><template #left-action><div style"display: flex"><IconMoon…...

SpringCloud+SpringCloudAlibaba学习笔记

SpringCloud 服务注册中心 eureka ap 高可用 分布式容错 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency><groupId…...

牛客小白月赛105(A~E)

文章目录 A lz的吃饭问题思路code B lz的数字问题思路code C lz的蛋挞问题思路code D lz的染色问题思路code E lz的括号问题思路code 总结 牛客小白月赛105 A lz的吃饭问题 思路 签到题&#xff0c;比较大小即可 code void solve(){int a,b,c,d;cin >> a >> b…...

OSPF协议整理

OSPF&#xff08;Open Shortest Path First&#xff09;即开放式最短路径优先协议&#xff0c;是一种广泛应用于大型网络中的链路状态路由协议。 OSPF 的基本概念 OSPF 是基于链路状态算法的内部网关协议&#xff08;IGP&#xff09;&#xff0c;用于在一个自治系统&#xff…...

Java中的多线程

文章目录 Java中的多线程一、引言二、多线程的创建和启动1、继承Thread类2、实现Runnable接口 三、线程的常用方法1、currentThread()和getName()2、sleep()和yield()3、join() 四、线程优先级五、使用示例六、总结 Java中的多线程 一、引言 在Java中&#xff0c;多线程编程是…...

什么是聚簇索引、非聚簇索引、回表查询

其实聚集索引也叫聚簇索引&#xff0c;二级索引也叫非聚簇索引&#xff0c;大家不要认为这是不同的两个知识点。 定义 先看一下数据库的索引介绍。 聚簇索引 1. 如果存在主键&#xff08;一般都存在&#xff09;&#xff0c;主键索引就是聚簇索引。 2. 如果不存在&#xff0c;…...

探索 Spring 框架核心组件:构建强大 Java 应用的基石

Spring框架作为Java企业级开发的首选框架之一&#xff0c;其强大的功能和灵活的架构深受开发者喜爱。Spring框架的核心组件共同构建了一个高效、可扩展的应用程序开发平台。本文将深入探讨Spring框架的核心组件&#xff0c;揭示它们如何在Spring框架中发挥关键作用。 一、Bean…...

Android 13 Aosp 默认允许应用动态权限

图库 frameworks/base/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java 修改 public void grantDefaultPermissions(int userId) {DelayingPackageManagerCache pm new DelayingPackageManagerCache();grantPermissionsToSysCompon…...

【C++知识总结1】c++第一篇,简单了解一下命名空间是什么

一、C的由来 C语言是一种结构化和模块化的编程语言&#xff0c;它对于处理较小规模的程序非常适用。然而&#xff0c;当面临需要高度抽象和建模的复杂问题&#xff0c;以及规模较大的程序时&#xff0c;C语言就显得不那么合适了。为了应对这种挑战&#xff0c;并在解决软件危机…...

从0开始深度学习(32)——循环神经网络的从零开始实现

本章将从零开始&#xff0c;基于循环神经网络实现字符级语言模型&#xff08;不是单词级&#xff09; 首先我们把从0开始深度学习&#xff08;30&#xff09;——语言模型和数据集中的load_corpus_time_machine()函数进行引用&#xff0c;用于导入数据&#xff1a; train_iter…...

GitLab使用操作v1.0

1.前置条件 Gitlab 项目地址&#xff1a;http://******/req Gitlab账户信息&#xff1a;例如 001/******自己的分支名称&#xff1a;例如 001-master&#xff08;注&#xff1a;master只有项目创建者有权限更新&#xff0c;我们只能更新自己分支&#xff0c;然后创建合并请求&…...

cuda conda yolov11 环境搭建

优雅的 yolo v11 标注工具 AutoLabel Conda环境直接识别训练 nvidia-smi 检查CUDA版本 下载nvidia cudnn对应的版本 将cuDNN压缩包内对应的文件复制到本地bin、include、lib的文件夹中 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.6 miniConda快速开始-安装 执行…...

解决SpringBoot连接Websocket报:请求路径 404 No static resource websocket.

问题发现 最近在工作中用到了WebSocket进行前后端的消息通信&#xff0c;后端代码编写完后&#xff0c;测试一下是否连接成功&#xff0c;发现报No static resource websocket.&#xff0c;看这个错貌似将接口变成了静态资源来访问了&#xff0c;第一时间觉得是端点没有注册成…...

element-plus的组件数据配置化封装 - table

目录 一、封装的table、table-column组件以及相关ts类型的定义 1、ATable组件的封装 - index.ts 2、ATableColumn组件的封装 - ATableColumn.ts 3、ATable、ATableColumn类型 - interface.ts 二、ATable、ATableColumn组件的使用 三、相关属性、方法的使用以及相关说明 1. C…...

【二维动态规划:交错字符串】

介绍 编程语言&#xff1a;Java 本篇介绍一道比较经典的二维动态规划题。 交错字符串 主要说明几点&#xff1a; 为什么双指针解不了&#xff1f;为什么是二维动态规划&#xff1f;根据题意分析处转移方程。严格位置依赖和空间压缩优化。 题目介绍 题意有点抽象&#xff0c…...

goframe开发一个企业网站 MongoDB 完整工具包18

1. MongoDB 工具包完整实现 (mongodb.go) package mongodbimport ("context""fmt""time""github.com/gogf/gf/v2/frame/g""go.mongodb.org/mongo-driver/mongo""go.mongodb.org/mongo-driver/mongo/options" )va…...

在vue中,根据后端接口返回的文件流实现word文件弹窗预览

需求 弹窗预览word文件&#xff0c;因浏览器无法直接根据blob路径直接预览word文件&#xff0c;所以需要利用插件实现。 解决方案 利用docx-preview实现word文件弹窗预览&#xff0c;以node版本16.21.3和docx-preview版本0.1.8为例 具体实现步骤 1、安装docx-preview插件 …...

动态规划之背包问题

0/1背包问题 1.二维数组解法 题目描述&#xff1a;有一个容量为m的背包&#xff0c;还有n个物品&#xff0c;他们的重量分别为w1、w2、w3.....wn&#xff0c;他们的价值分别为v1、v2、v3......vn。每个物品只能使用一次&#xff0c;求可以放进背包物品的最大价值。 输入样例…...

【Python】 深入理解Python的单元测试:用unittest和pytest进行测试驱动开发

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 单元测试是现代软件开发中的重要组成部分,通过验证代码的功能性、准确性和稳定性,提升代码质量和开发效率。本文章深入介绍Python中两种主流单元测试框架:unittest和pytest,并结合测试驱动开发(TDD)…...

Java集合1.0

1.什么是集合&#xff1f; 集合就是一个存放数据的容器&#xff0c;准确的说是放数据对象引用的容器。 集合和数组的区别 数组是固定长度&#xff0c;集合是可变长度。数组可以存储基本数据类型&#xff0c;也可以存储引用数据类型&#xff0c;集合只能存储引用数据类型&…...

Leetcode 336 回文对

示例 1&#xff1a; 输入&#xff1a;words ["abcd","dcba","lls","s","sssll"] 输出&#xff1a;[[0,1],[1,0],[3,2],[2,4]] 解释&#xff1a;可拼接成的回文串为 ["dcbaabcd","abcddcba","sl…...

实现一个可配置的TCP设备模拟器,支持交互和解析配置

前言 诸位在做IOT开发的时候是否有遇到一个问题&#xff0c;那就是模拟一个设备来联调测试&#xff0c;虽然说现在的物联网通信主要是用mqtt通信&#xff0c;但还是有很多设备使用TCP这种协议交互&#xff0c;例如充电桩&#xff0c;还有一些工业设备&#xff0c;TCP这类报文交…...

算法的空间复杂度

空间复杂度 空间复杂度主要是衡量一个算法运行所需要的额外空间&#xff0c;在计算机发展早期&#xff0c;计算机的储存容量很小&#xff0c;所以空间复杂度是很重要的。但是经过计算机行业的迅速发展&#xff0c;计算机的容量已经不再是问题了&#xff0c;所以如今已经不再需…...

自定义协议

1. 问题引入 问题&#xff1a;TCP是面向字节流的&#xff08;TCP不关心发送的数据是消息、文件还是其他任何类型的数据。它简单地将所有数据视为一个字节序列&#xff0c;即字节流。这意味着TCP不会对发送的数据进行任何特定的边界划分&#xff0c;它只是确保数据的顺序和完整…...

在 Taro 中实现系统主题适配:亮/暗模式

目录 背景实现方案方案一&#xff1a;CSS 变量 prefers-color-scheme 媒体查询什么是 prefers-color-scheme&#xff1f;代码示例 方案二&#xff1a;通过 JavaScript 监听系统主题切换 背景 用Taro开发的微信小程序&#xff0c;需求是页面的UI主题想要跟随手机系统的主题适配…...

autogen框架中使用chatglm4模型实现react

本文将介绍如何使用使用chatglm4实现react&#xff0c;利用环境变量、Tavily API和ReAct代理模式来回答用户提出的问题。 环境变量 首先&#xff0c;我们需要加载环境变量。这可以通过使用dotenv库来实现。 from dotenv import load_dotenv_ load_dotenv()注意.env文件处于…...

读《Effective Java》笔记 - 条目9

条目9&#xff1a;与try-finally 相比&#xff0c;首选 try -with -resource 什么是 try-finally&#xff1f; try-finally 是 Java 中传统的资源管理方式&#xff0c;通常用于确保资源&#xff08;如文件流、数据库连接等&#xff09;被正确关闭。 BufferedReader reader n…...

【软件入门】Git快速入门

Git快速入门 文章目录 Git快速入门0.前言1.安装和配置2.新建版本库2.1.本地创建2.2.云端下载 3.版本管理3.1.添加和提交文件3.2.回退版本3.2.1.soft模式3.2.2.mixed模式3.2.3.hard模式3.2.4.使用场景 3.3.查看版本差异3.4.忽略文件 4.云端配置4.1.Github4.1.1.SSH配置4.1.2.关联…...

nextjs window is not defined

问题产生的原因 在 Next.js 中&#xff0c;“window is not defined” 错误通常出现在服务器端渲染&#xff08;Server - Side Rendering&#xff0c;SSR&#xff09;的代码中。这是因为window对象是浏览器环境中的全局对象&#xff0c;在服务器端没有window这个概念。例如&am…...