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

Qt 监控USB设备的插入和移除

Qt 监控USB设备的插入和移除

flyfish

Ubuntu22.04
Qt 6.2.4

CMakeLists.txt 内容

# 指定 CMake 的最低版本要求
cmake_minimum_required(VERSION 3.16)# 定义项目的名称和使用的编程语言
project(USBMonitor LANGUAGES CXX)# 开启自动 UIC,MOC 和 RCC 工具
set(CMAKE_AUTOUIC ON)       # 自动运行 uic 工具处理 .ui 文件
set(CMAKE_AUTOMOC ON)       # 自动运行 moc 工具处理 Qt 的元对象系统
set(CMAKE_AUTORCC ON)       # 自动运行 rcc 工具处理资源文件# 设置 C++ 标准为 C++17,并且要求编译器必须支持 C++17
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)# 查找 Qt 库,并指定所需的组件
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core libudev)  # 尝试查找 Qt6 或 Qt5 库,并且要求 Core 和 libudev 组件
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core)     # 根据找到的 Qt 版本(Qt6 或 Qt5),再次查找 Core 组件# 包含 GNUInstallDirs 模块,该模块定义了一些标准的安装目录变量
include(GNUInstallDirs)# 定义一个可执行目标 USBMonitor,并指定其源文件为 main.cpp
add_executable(USBMonitormain.cpp
)# 将 Qt Core 库和 udev 库链接到 USBMonitor 可执行文件
target_link_libraries(USBMonitor PRIVATE Qt${QT_VERSION_MAJOR}::Core udev)# 定义安装目标 USBMonitor 的安装路径
install(TARGETS USBMonitorLIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}  # 指定库文件的安装路径RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}  # 指定可执行文件的安装路径
)

编译过程中的内部操作

  1. 解析 CMakeLists.txt

    • CMake 读取 CMakeLists.txt 文件,解析其中的指令和变量设置。
  2. 查找和配置依赖库

    • find_package 命令会查找 Qt 库和 libudev 库,并设置相应的变量,如 Qt6::CoreUDEV_LIBRARIES
  3. 生成构建文件

    • CMake 生成适合特定构建系统的文件,如 Makefile(对于 Unix 系统)或 Visual Studio 项目文件(对于 Windows 系统)。
  4. 编译源文件

    • 构建系统(如 make)根据生成的构建文件编译源文件 main.cpp,生成目标文件 main.cpp.o
  5. 链接目标文件

    • 构建系统将目标文件 main.cpp.oQt6::Coreudev 库链接,生成最终的可执行文件 USBMonitor
  6. 安装目标(可选):

    • 如果运行 make install 命令,构建系统会将生成的可执行文件 USBMonitor 安装到指定的目录,如 /usr/local/bin

源码

#include <QCoreApplication>  // 包含 Qt 核心模块
#include <QSocketNotifier>   // 包含 QSocketNotifier 类
#include <QDebug>            // 包含调试输出功能
#include <unistd.h>          // 包含 POSIX 操作系统 API
#include <fcntl.h>           // 包含文件控制选项
#include <libudev.h>         // 包含 libudev 库
#include <sys/inotify.h>     // 包含 inotify API
#include <errno.h>           // 包含错误码定义// 定义 UsbMonitor 类,继承自 QObject
class UsbMonitor : public QObject {Q_OBJECT  // 宏,用于 Qt 元对象系统public:// 构造函数UsbMonitor(QObject *parent = nullptr) : QObject(parent) {// 初始化 udevudev = udev_new();  // 创建 udev 上下文if (!udev) {qCritical() << "无法初始化 udev";  // 如果初始化失败,输出错误信息return;}// 创建 udev 监视器udev_monitor = udev_monitor_new_from_netlink(udev, "udev");  // 创建 udev 监视器if (!udev_monitor) {qCritical() << "无法创建 udev 监视器";  // 如果创建失败,输出错误信息return;}// 过滤只监听 USB 设备udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "usb", NULL);  // 过滤只监听 USB 子系统的设备udev_monitor_enable_receiving(udev_monitor);  // 启用接收事件// 获取 udev 监视器的文件描述符int udev_fd = udev_monitor_get_fd(udev_monitor);  // 获取 udev 监视器的文件描述符// 检查文件描述符是否有效if (fcntl(udev_fd, F_GETFD) == -1) {  // 使用 fcntl 检查文件描述符是否有效qCritical() << "无效的文件描述符:" << udev_fd;  // 如果无效,输出错误信息return;}// 创建 QSocketNotifier 并连接信号notifier = new QSocketNotifier(udev_fd, QSocketNotifier::Read, this);  // 创建 QSocketNotifier 对象connect(notifier, &QSocketNotifier::activated, this, &UsbMonitor::onUdevEvent);  // 连接 QSocketNotifier 的 activated 信号到 onUdevEvent 槽函数}// 析构函数~UsbMonitor() {if (notifier) {delete notifier;  // 释放 QSocketNotifier 对象}if (udev_monitor) {udev_monitor_unref(udev_monitor);  // 释放 udev 监视器}if (udev) {udev_unref(udev);  // 释放 udev 上下文}}private slots:// 处理 udev 事件的槽函数void onUdevEvent(int socket) {// 从 udev 监视器读取事件struct udev_device *dev = udev_monitor_receive_device(udev_monitor);  // 从 udev 监视器读取设备事件if (dev) {const char *action = udev_device_get_action(dev);  // 获取事件动作const char *devnode = udev_device_get_devnode(dev);  // 获取设备节点const char *devtype = udev_device_get_devtype(dev);  // 获取设备类型if (action && devnode && devtype) {if (strcmp(action, "add") == 0) {qDebug() << "USB 设备插入:" << devnode;  // 如果是插入事件,输出设备节点} else if (strcmp(action, "remove") == 0) {qDebug() << "USB 设备移除:" << devnode;  // 如果是移除事件,输出设备节点}}udev_device_unref(dev);  // 释放 udev_device 对象}}private:struct udev *udev;  // udev 上下文struct udev_monitor *udev_monitor;  // udev 监视器QSocketNotifier *notifier;  // QSocketNotifier 对象
};// 主函数
int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);  // 创建 QCoreApplication 对象UsbMonitor monitor;  // 创建 UsbMonitor 对象return a.exec();  // 进入事件循环
}#include "main.moc"  // 包含 moc 生成的代码

运行结果

USB 设备移除: /dev/bus/usb/002/006
USB 设备插入: /dev/bus/usb/002/007
USB 设备移除: /dev/bus/usb/002/007
USB 设备插入: /dev/bus/usb/002/008

#include "main.moc" 的主要作用是确保 moc(Meta-Object Compiler)生成的代码能够被编译器正确处理。具体来说,moc 会生成一些额外的代码,这些代码需要被包含在源文件的末尾,以便在编译时能够正确链接和使用。

Qt 元对象系统:

Qt 的元对象系统提供了信号和槽机制、运行时类型信息(RTTI)、动态属性系统等功能。
为了实现这些功能,Qt 提供了一个工具 moc(Meta-Object Compiler),它会生成一些额外的 C++ 代码。

moc 生成的代码:

当在类中使用 Q_OBJECT 宏时,moc 会为该类生成一些额外的代码,这些代码包括信号和槽的实现、元对象数据等。
生成的代码通常保存在一个与源文件同名的 .moc 文件中,例如 main.cpp 对应的 main.moc。

包含 main.moc:

为了确保生成的代码能够被编译器正确处理,需要在源文件的末尾包含 main.moc 文件。
这样做是为了确保 moc 生成的代码在编译时能够被正确链接到的源文件中。

编译顺序:

编译器在处理源文件时,会按照文件中出现的顺序依次处理每一行代码。
如果在源文件的开头包含 main.moc,可能会导致编译器在处理 moc 生成的代码时,还没有看到类的定义,从而引发编译错误。
因此,通常在源文件的末尾包含 main.moc,确保类的定义已经完全可见。

libudev 库提供的函数 ,用于管理和监控硬件设备的变化。

1. udev_new()

  • 原型struct udev *udev_new(void);
  • 作用:创建一个新的 udev 上下文。
  • 返回值:成功时返回指向 udev 结构的指针,失败时返回 NULL
  • 示例
    udev = udev_new();
    if (!udev) {qCritical() << "无法初始化 udev";return;
    }
    

2. udev_monitor_new_from_netlink()

  • 原型struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name);
  • 作用:创建一个新的 udev 监视器,用于监听来自内核的 netlink 事件。
  • 参数
    • udevudev 上下文。
    • name:监视器的名称,通常为 "udev"
  • 返回值:成功时返回指向 udev_monitor 结构的指针,失败时返回 NULL
  • 示例
    udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
    if (!udev_monitor) {qCritical() << "无法创建 udev 监视器";return;
    }
    

3. udev_monitor_filter_add_match_subsystem_devtype()

  • 原型int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, const char *subsystem, const char *devtype);
  • 作用:为 udev 监视器添加一个过滤器,只监听特定子系统和设备类型的事件。
  • 参数
    • udev_monitorudev 监视器。
    • subsystem:要监听的子系统,例如 "usb"
    • devtype:要监听的设备类型,可以为 NULL 表示匹配所有设备类型。
  • 返回值:成功时返回 0,失败时返回负数。
  • 示例
    udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "usb", NULL);
    

4. udev_monitor_enable_receiving()

  • 原型int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor);
  • 作用:启用 udev 监视器,开始接收事件。
  • 参数
    • udev_monitorudev 监视器。
  • 返回值:成功时返回 0,失败时返回负数。
  • 示例
    udev_monitor_enable_receiving(udev_monitor);
    

5. udev_monitor_get_fd()

  • 原型int udev_monitor_get_fd(struct udev_monitor *udev_monitor);
  • 作用:获取 udev 监视器的文件描述符,用于监听事件。
  • 参数
    • udev_monitorudev 监视器。
  • 返回值:成功时返回文件描述符,失败时返回负数。
  • 示例
    int udev_fd = udev_monitor_get_fd(udev_monitor);
    

6. udev_monitor_receive_device()

  • 原型struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor);
  • 作用:从 udev 监视器中读取下一个设备事件。
  • 参数
    • udev_monitorudev 监视器。
  • 返回值:成功时返回指向 udev_device 结构的指针,失败时返回 NULL
  • 示例
    struct udev_device *dev = udev_monitor_receive_device(udev_monitor);
    

7. udev_device_get_action()

  • 原型const char *udev_device_get_action(struct udev_device *udev_device);
  • 作用:获取设备事件的动作(例如 "add""remove")。
  • 参数
    • udev_device:设备对象。
  • 返回值:成功时返回动作字符串,失败时返回 NULL
  • 示例
    const char *action = udev_device_get_action(dev);
    

8. udev_device_get_devnode()

  • 原型const char *udev_device_get_devnode(struct udev_device *udev_device);
  • 作用:获取设备的设备节点路径(例如 /dev/sdb1)。
  • 参数
    • udev_device:设备对象。
  • 返回值:成功时返回设备节点路径字符串,失败时返回 NULL
  • 示例
    const char *devnode = udev_device_get_devnode(dev);
    

9. udev_device_get_devtype()

  • 原型const char *udev_device_get_devtype(struct udev_device *udev_device);
  • 作用:获取设备的类型(例如 "disk""partition")。
  • 参数
    • udev_device:设备对象。
  • 返回值:成功时返回设备类型字符串,失败时返回 NULL
  • 示例
    const char *devtype = udev_device_get_devtype(dev);
    

10. udev_device_unref()

  • 原型void udev_device_unref(struct udev_device *udev_device);
  • 作用:释放 udev_device 对象,减少其引用计数。
  • 参数
    • udev_device:设备对象。
  • 示例
    udev_device_unref(dev);
    

11. udev_monitor_unref()

  • 原型void udev_monitor_unref(struct udev_monitor *udev_monitor);
  • 作用:释放 udev_monitor 对象,减少其引用计数。
  • 参数
    • udev_monitorudev 监视器。
  • 示例
    udev_monitor_unref(udev_monitor);
    

12. udev_unref()

  • 原型void udev_unref(struct udev *udev);
  • 作用:释放 udev 上下文对象,减少其引用计数。
  • 参数
    • udevudev 上下文。
  • 示例
    udev_unref(udev);
    

相关文章:

Qt 监控USB设备的插入和移除

Qt 监控USB设备的插入和移除 flyfish Ubuntu22.04 Qt 6.2.4 CMakeLists.txt 内容 # 指定 CMake 的最低版本要求 cmake_minimum_required(VERSION 3.16)# 定义项目的名称和使用的编程语言 project(USBMonitor LANGUAGES CXX)# 开启自动 UIC&#xff0c;MOC 和 RCC 工具 set(…...

终于弄懂了Python自定义模块与代码复用

自定义模块与代码复用 在编写Python代码时&#xff0c;很多时候我们会遇到需要多次使用相同功能的情况。这时候&#xff0c;模块化编程就显得尤为重要。通过将常用的功能代码放入单独的模块中&#xff0c;我们可以轻松地进行代码复用&#xff0c;避免重复编写相同的代码&#…...

从无音响Windows 端到 有音响macOS 端实时音频传输播放

以下是从 Windows 端到 macOS 端传输音频的优化方案&#xff0c;基于上述链接中的思路进行调整&#xff1a; Windows 端操作 安装必要软件 安装 Python&#xff08;确保版本兼容且已正确配置环境变量&#xff09;。安装 PyAudio 库&#xff0c;可通过 pip install pyaudio 命令…...

直方图均衡化及Matlab实现

文章目录 直方图均衡化关键点及思路Matlab实现 直方图均衡化 直方图均衡化是一种图像增强技术&#xff0c;主要用于增强图像的对比度&#xff0c;特别是当图像的有用数据的对比度接近时效果显著。通过改变图像的直方图分布&#xff0c;直方图均衡化能够使图像的灰度值更加接近…...

设备接入到NVR管理平台EasyNVR多品牌NVR管理工具/设备的音视频配置参考

NVR管理平台EasyNVR是一款功能强大的安防视频监控平台&#xff0c;能够轻松实现视频流的导入、录像、存储和回放等功能。在将设备接入到海康NVR管理平台EasyNVR时&#xff0c;视音频配置是确保视频监控效果的重要步骤。本文将详细介绍如何将设备接入到EasyNVR平台&#xff0c;并…...

后端:Aop 面向切面编程

文章目录 1. Aop 初步学习面向切面编程&#xff0c;EnableAspectJAutoProxy2. AOP的核心概念3. 前置通知&#xff08;Before&#xff09;4. 后置通知&#xff08;After&#xff09;5. 返回通知&#xff08;AfterReturning&#xff09;6. 异常通知&#xff08;AfterThrowing&…...

大数据机器学习算法与计算机视觉应用02:线性规划

Linear Programming Definition of linear programmingmax and min-cost max flowlinear program to solve minimax optimal strategies in gamesAlgoithms for linear programmingl1 regressionSeidel’s 2-dimensional linear programming algorithm linear program 线性规…...

godot——主题、Theme、StyleBox

我刚开始被这些术语吓到了&#xff0c;一直不敢去接触它们&#xff0c;都用的默认样式。现在好不容易有点思路了&#xff0c;记录下来。 下面看看怎么自定义样式。 1.先新建一个Theme 2.再次点击创建好的Theme 得到 图1 这样一个面板。&#xff08;看不懂没事&#xff0c;继…...

深入理解接口测试:实用指南与最佳实践5.0(一)

✨博客主页&#xff1a; https://blog.csdn.net/m0_63815035?typeblog &#x1f497;《博客内容》&#xff1a;.NET、Java.测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识 &#x1f4e2;博客专栏&#xff1a; https://blog.csdn.net/m0_63815035/cat…...

SQL面试题——飞猪SQL面试 重点用户

飞猪SQL面试题—重点用户 在一些场景中我们经常听到这样的一些描述&#xff0c;例如20%的用户贡献了80%的销售额&#xff0c;或者是20%的人拥有着80%的财富&#xff0c;你知道这样的数据是怎么算出来的吗 数据如下,uid 是用户的id ,amount是用户的消费金额 |uid|amount| ---…...

Angular 和 Vue2.0 对比

前言 &#xff1a;“业精于勤&#xff0c;荒于嬉&#xff1b;行成于思&#xff0c;毁于随” 很久没写博客了&#xff0c;大多记录少进一步探查。 Angular 和 Vue2.0 对比&#xff1a; 一.概念 1.1 Angular 框架&#xff1a; 是一款由谷歌开发的开源web前端框架&#xff08;核…...

websocket服务器(协程风格)--swoole进阶篇

swoole的websocket服务器(协程风格)示例真不算友善,从头了解到尾,那还好,但是谁有那么多时间从头到尾了解。示例不够针对性,写websocket就该单独写websocket的东西,偏偏又加上http的东西。这里我来解读一下websocket服务器(协程风格)示例 <?php use Swoole\Http\…...

Windows C/C++ Socket 编程

承接上文&#xff1a;socket 编程 本文目录 Windows Client 端WSADATA 结构体WSAStartup() 函数SOCKET 以及 socket() 函数sockaddr_ininet_pton() 函数in_addr structmemcpy()connect() 函数send() 函数recv() 函数 Windows Server 端 在进行 socket 编程之前&#xff0c;你要…...

计算两个结构的乘法

在行列可自由变换的平面上&#xff0c;2点结构有3个 3点结构有6个 计算2*2 2a1*2a14a6 2a1*2a24a8 2a1*2a34a12 显然2a1*2a14a6因为这3个结构都分布在同一列上&#xff0c;就是整数乘法。2a1*2a2的结果有2种写法&#xff0c;一种外形像2a1细节为2a2&#xff0c;一种外形为2…...

学校服务器连接pycharm配置2

上一个可能还是有点问题&#xff0c;因为实际在跑的时候读取的其实是本地的anaconda&#xff0c;这个重新整了一下流程 首先在学校服务器先激活自己创建的虚拟环境&#xff0c;这里就不截图了 然后在pycharm里面打开设置 选择这个python解释器 这里有添加解释器 选择SSH …...

AI赋能电商:创新应用提升销售与用户体验

目录 一、引言 二、AI技术在电商领域的创新应用 三、AI技术提高电商销售效率和用户体验的实践路径 一、引言 随着人工智能&#xff08;AI&#xff09;技术的不断成熟&#xff0c;电商行业正迎来一场深刻的变革。AI技术在购物推荐、会员分类、商品定价等方面的创新应用&…...

详解kafka消息发送重试机制的案例

在 Kafka 生产者中实现消息发送的重试机制&#xff0c;可以通过配置 KafkaProducer 的相关属性来实现。以下是一些关键的配置项&#xff1a; retries&#xff1a;设置生产者发送失败后重试的次数。 retry.backoff.ms&#xff1a;设置生产者在重试前等待的时间。 buffer.memo…...

linux文本管理!!!

文章目录 第1章 文本过滤/查看命令1.echo&#xff1a;输出文本2.cat&#xff1a;合并文件或查看文件内容3.head&#xff1a;显示文件头部信息4.tail&#xff1a;显示文件尾部信息5.wc: 统计文本行号6.less&#xff1a;分页显示文件内容7.grep&#xff1a;文本过滤工具8.定向符号…...

软件设计师-计算机体系结构分类

计算机体系结构分类 Flynn分类法 根据不同的指令流数据流组织方式分类单指令流但数据流SISD,单处理器系统单指令多数据流SIMD&#xff0c;单指令流多数据流是一种采用一个控制器来控制多个处理器&#xff0c;同时对一组数据&#xff08;又称“数据矢量”&#xff09;中的每一…...

《基于深度学习的车辆行驶三维环境双目感知方法研究》

复原论文思路&#xff1a; 《基于深度学习的车辆行驶三维环境双目感知方法研究》 1、双目测距的原理 按照上述公式算的话&#xff0c;求d的话&#xff0c;只和xl-xr有关系&#xff0c;这样一来&#xff0c;是不是只要两张图像上一个测试点的像素位置确定&#xff0c;对应的深…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

html-<abbr> 缩写或首字母缩略词

定义与作用 <abbr> 标签用于表示缩写或首字母缩略词&#xff0c;它可以帮助用户更好地理解缩写的含义&#xff0c;尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时&#xff0c;会显示一个提示框。 示例&#x…...

LabVIEW双光子成像系统技术

双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制&#xff0c;展现出显著的技术优势&#xff1a; 深层组织穿透能力&#xff1a;适用于活体组织深度成像 高分辨率观测性能&#xff1a;满足微观结构的精细研究需求 低光毒性特点&#xff1a;减少对样本的损伤…...

水泥厂自动化升级利器:Devicenet转Modbus rtu协议转换网关

在水泥厂的生产流程中&#xff0c;工业自动化网关起着至关重要的作用&#xff0c;尤其是JH-DVN-RTU疆鸿智能Devicenet转Modbus rtu协议转换网关&#xff0c;为水泥厂实现高效生产与精准控制提供了有力支持。 水泥厂设备众多&#xff0c;其中不少设备采用Devicenet协议。Devicen…...

若依登录用户名和密码加密

/*** 获取公钥&#xff1a;前端用来密码加密* return*/GetMapping("/getPublicKey")public RSAUtil.RSAKeyPair getPublicKey() {return RSAUtil.rsaKeyPair();}新建RSAUti.Java package com.ruoyi.common.utils;import org.apache.commons.codec.binary.Base64; im…...

链式法则中 复合函数的推导路径 多变量“信息传递路径”

非常好&#xff0c;我们将之前关于偏导数链式法则中不能“约掉”偏导符号的问题&#xff0c;统一使用 二重复合函数&#xff1a; z f ( u ( x , y ) , v ( x , y ) ) \boxed{z f(u(x,y),\ v(x,y))} zf(u(x,y), v(x,y))​ 来全面说明。我们会展示其全微分形式&#xff08;偏导…...

精益数据分析(98/126):电商转化率优化与网站性能的底层逻辑

精益数据分析&#xff08;98/126&#xff09;&#xff1a;电商转化率优化与网站性能的底层逻辑 在电子商务领域&#xff0c;转化率与网站性能是决定商业成败的核心指标。今天&#xff0c;我们将深入解析不同类型电商平台的转化率基准&#xff0c;探讨页面加载速度对用户行为的…...