深入浅出 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 Thread

app = 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
    
    # 获取客户端ID
    client_id = request.sid
    
    # 将客户端加入房间
    join_room(room)
    client_rooms[client_id] = room
    
    print(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:
        return
    
    client_id = request.sid
    leave_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.sid
    if 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'] = False
                data_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数据可视化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/923756.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一键AI换脸软件,支持表情控制,唇形同步Facefusion-3.0.0发布!支持N卡和CPU,一键启动包

嗨,小伙伴们!还记得小编之前介绍的FaceFusion 2.6.1吗?今天给大家带来超级exciting的消息 —— FaceFusion 3.0.0闪亮登场啦! &#x1f31f; 3.0.0版本更新 &#x1f3d7;️ 全面重构:修复了不少小虫子,运行更稳定,再也不怕突然罢工啦! &#x1f600; Live Portrait功能:新增…

spring boot框架漏洞复现

spring - java开源框架有五种 Spring MVC、SpringBoot、SpringFramework、SpringSecurity、SpringCloud spring boot版本 版本1: 直接就在根下 / 版本2:根下的必须目录 /actuator/ 端口:9093 spring boot搭建 1:直接下载源码打包 2:运行编译好的jar包:actuator-testb…

hhdb数据库介绍(10-8)

首页 管理平台通过数据可视方式在首页功能中实时展示计算节点集群的数据量、访问流量、集群组件状态、告警事件、安全防控等用户关心的信息。 集群安全 邮件通知&#xff1a;根据通知设置中监控开关是否打开判断&#xff0c;分为&#xff1a;全部开启、未开启、部分开启&…

Vue前端开发-slot传参

slot 又称插槽&#xff0c;它是在子组件中为父组件提供的一个占位符&#xff0c;使用来表示&#xff0c;通过这个占位符&#xff0c;父组件可以向中填充任意的内容代码&#xff0c;这些代码将自动替换占位符的位置&#xff0c;从而轻松实现在父组件中控制子组件内容的需求。 作…

18:(标准库)DMA二:DMA+串口收发数据

DMA串口收发数据 1、DMA串口发送数据2、DMA中断串口接收定长数据包3、串口空闲中断DMA接收不定长数据包 1、DMA串口发送数据 当串口的波特率大于115200时&#xff0c;可以通过DMA1进行数据搬运&#xff0c;以防止数据的丢失。如上图所示&#xff1a;UART1的Tx发送请求使用DMA1的…

2024 java大厂面试复习总结(一)(持续更新)

10年java程序员&#xff0c;2024年正好35岁&#xff0c;2024年11月公司裁员&#xff0c;记录自己找工作时候复习的一些要点。 java基础 hashCode()与equals()的相关规定 如果两个对象相等&#xff0c;则hashcode一定也是相同的两个对象相等&#xff0c;对两个对象分别调用eq…

深度学习5

一、模型保存与加载 1、序列化方式 保存方式&#xff1a;torch.save(model, "model.pkl") 打开方式&#xff1a;model torch.load("model.pkl", map_location"cpu") ​ import torch import torch.nn as nnclass MyModle(nn.Module):def __ini…

Redis五大基本类型——Zset有序集合命令详解(命令用法详解+思维导图详解)

目录 一、Zset有序集合类型介绍 二、常见命令 1、ZADD 2、ZCARD 3、ZCOUNT 4、ZRANGE 5、ZREVRANGE 6、ZRANGEBYSCORE 7、ZREVRANGEBYSCORE 8、ZPOPMAX 9、ZPOPMIN 10、ZRANK 11、ZREVRANK 12、ZSCORE 13、ZREM 14、ZREMRANGEBYRANK 15、ZREMRANGEBYSCORE 16…

ARM架构 AArch64 基础知识介绍

介绍 aarch64是 ARM 架构的 64 位版本&#xff0c;它是 ARMv8 架构的一部分&#xff0c;被设计用来提供更高的性能和更大的地址空间&#xff0c;同时保持与 32 位 ARM 架构的兼容性。AArch64 是 ARMv8 的 64 位指令集架构&#xff08;ISA&#xff09;&#xff0c;它提供了丰富的…

Rust中Tracing 应用指南

欢迎来到这篇全面的Rust跟踪入门指南。Rust 的tracing是一个用于应用程序级别的诊断和调试的库。它提供了一种结构化的、异步感知的方式来记录日志和跟踪事件。与传统的日志记录相比&#xff0c;tracing能够更好地处理复杂的异步系统和分布式系统中的事件跟踪&#xff0c;帮助开…

极狐GitLab 17.6 正式发布几十项与 DevSecOps 相关的功能【三】

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 官网极狐…

WinFrom调用webapi接口另一个方法及其应用实例

1.调用接口方法 代码如下&#xff1a; public class WebAPI{#region WebAPI调用 public async Task<string> Call_Webapi(string Url, string Json) //url传入的是接口名称&#xff0c;json传入的是接口参数{string responseBody string.Empty; //responseBod…

elasticsearch的索引模版使用方法

5 索引模版⭐️⭐️⭐️⭐️⭐️ 索引模板就是创建索引时要遵循的模板规则索引模板仅对新创建的索引有效&#xff0c;已经创建的索引并不受索引模板的影响 5.1 索引模版的基本使用 1.查看所有的索引模板 GET 10.0.0.91:9200/_index_template2.创建自定义索引模板 xixi &…

从零开始学GeoServer源码(二)添加支持arcgis切片功能

文章目录 参考文章环境背景1、配置打包好的程序1.1、下载GeoServer的war包1.2、下载GeoWebCache1.3、拷贝jar包1.4、修改配置文件1.4.1、拷贝geowebcache-arcgiscache-context.xml1.4.2、修改geowebcache-core-context.xml1.4.3、修改geowebcache-servlet.xml 1.5、配置切片信息…

Redis 可观测最佳实践

Redis 介绍 Redis 是一个开源的高性能键值对&#xff08;key-value&#xff09;数据库。它通常用作数据库、缓存和消息代理。Redis 支持多种类型的数据结构&#xff0c;Redis 通常用于需要快速访问的场景&#xff0c;如会话缓存、全页缓存、排行榜、实时分析等。由于其高性能和…

HarmonyOs鸿蒙开发实战(21)=>组件间通信@ohos/liveeventbus

1.简介 LiveEventBus是一款消息总线&#xff0c;具有生命周期感知能力&#xff0c;支持Sticky&#xff0c;支持跨进程&#xff0c;支持跨APP发送消息。 2.下载安装 ohpm install ohos/liveeventbus 3.订阅&#xff0c;注册监听 4.发送事件 5. 完成 > 记得关注博主&#xff…

深度学习使用LSTM实现时间序列预测

大家好&#xff0c;LSTM是一种特殊的循环神经网络&#xff08;RNN&#xff09;架构&#xff0c;它被设计用来解决传统RNN在处理长序列数据时的梯度消失和梯度爆炸问题&#xff0c;特别是在时间序列预测、自然语言处理和语音识别等领域中表现出色。LSTM的核心在于其独特的门控机…

用Tauri框架构建跨平台桌面应用:1、Tauri快速开始

Tauri 是一个构建适用于所有主流桌面和移动平台的轻快二进制文件的框架。开发者们可以集成任何用于创建用户界面的可以被编译成 HTML、JavaScript 和 CSS 的前端框架&#xff0c;同时可以在必要时使用 Rust、Swift 和 Kotlin 等语言编写后端逻辑。 Tauri 是什么&#xff1f; |…

Elasticsearch对于大数据量(上亿量级)的聚合如何实现?

大家好&#xff0c;我是锋哥。今天分享关于【Elasticsearch对于大数据量&#xff08;上亿量级&#xff09;的聚合如何实现&#xff1f;】面试题。希望对大家有帮助&#xff1b; Elasticsearch对于大数据量&#xff08;上亿量级&#xff09;的聚合如何实现&#xff1f; 1000道 …

C语言:C语言实现对MySQL数据库表增删改查功能

基础DOME可以用于学习借鉴&#xff1b; 具体代码 #include <stdio.h> #include <mysql.h> // mysql 文件&#xff0c;如果配置ok就可以直接包含这个文件//宏定义 连接MySQL必要参数 #define SERVER "localhost" //或 127.0.0.1 #define USER "roo…