【Qt】modbus客户端笔记

Qt 中基于 Modbus 协议的通用客户端学习笔记

一、概述

本客户端利用 Qt 的 QModbusTcpClient 实现与 Modbus 服务器的通信,具备连接、读写寄存器、心跳检测、自动重连等功能,旨在提供一个可靠且易用的 Modbus 客户端框架,方便在不同项目中集成使用。

二、核心功能实现

(一)初始化

  1. 在构造函数中:
    • 首先初始化基类 QObject,确保对象层次结构正确构建。
    • 实例化 m_modbusClient,将自身作为其父对象,以便管理内存。
    • 创建 m_heartbeatTimerm_reconnectTimer,并设置心跳间隔(如 2 秒)和自动重连间隔(如 2 秒)。
    • 连接相关信号与槽:
      • m_heartbeatTimer 超时,触发 checkHeartbeat 槽函数,用于检测心跳。
      • m_reconnectTimer 超时,触发 attemptReconnect 槽函数,尝试重新连接服务器。
      • m_modbusClient 的状态改变时,连接到 handleStateChanged 槽函数,以便及时更新设备连接状态。

(二)连接管理

  1. 获取连接状态
    • getStatus 函数通过查询 m_modbusClient 的当前状态(QModbusDevice::State),将其转换为自定义的 DeviceStatus(如 ConnectedConnectingDisconnected)枚举类型,并返回。这有助于上层代码了解设备的实时连接情况。
  2. 连接设备
    • connectDevice 函数首先从 m_properties 中获取存储的 IP 地址和端口信息,分别设置到 m_modbusClient 的连接参数中(使用 QModbusDevice::NetworkAddressParameterQModbusDevice::NetworkPortParameter),然后调用 m_modbusClientconnectDevice 方法尝试建立连接。根据连接结果,通过 setStatus 函数更新设备状态。
  3. 断开连接
    • disconnectDevice 函数调用 m_modbusClientdisconnectDevice 方法断开与服务器的连接,同时停止心跳定时器和自动重连定时器,并更新设备状态为 Disconnected

(三)读写寄存器

  1. 写寄存器
    • writeRegisters 函数首先检查设备是否已连接(通过 m_modbusClient 的状态判断),若未连接则打印错误信息并返回 false。接着,构建 QModbusDataUnit 对象,设置要写入的寄存器类型、起始地址以及数据值。然后,通过 m_modbusClient 发送写请求,并连接到回复信号,在回复完成后根据错误码判断写入操作是否成功。
  2. 读寄存器
    • readRegisters 函数同样先检查设备连接状态,若未连接则打印错误信息并返回。构建 QModbusDataUnit 对象指定要读取的寄存器类型、起始地址和数量,然后通过 m_modbusClient 发送读请求,并连接到 onAllRegistersReadReady 槽函数,等待读取结果。

(四)心跳检测与自动重连

  1. 心跳检测
    • checkHeartbeat 函数在心跳定时器超时时触发。它首先检查设备是否仍处于连接状态,若不是则直接处理连接断开情况(调用 handleStateChanged 函数将状态设为 Unconnected)。若连接正常,则向服务器发送一个简单的读寄存器请求(通常读取保持寄存器的特定位置,如 0 地址,长度为 1),通过回复的错误码判断连接是否依然存活,若有错误则同样处理连接断开。
  2. 自动重连
    • attemptReconnect 函数在自动重连定时器超时时执行。它检查设备是否未连接,若是,则尝试重新连接(调用 connectDevice 函数),并记录重连次数。若重连次数达到预设上限(如 MAX_RECONNECT_ATTEMPTS),则停止自动重连定时器,防止无意义的重复尝试。

三、信号与槽

  1. m_modbusClient 的状态改变时,handleStateChanged 槽函数被触发,根据不同的状态(UnconnectedStateConnectedStateConnectingState)更新设备的自定义连接状态,并触发相应操作,如启动或停止定时器。
  2. 在读寄存器操作完成后,onAllRegistersReadReady 槽函数被调用,它从 QModbusReply 中获取读取结果数据单元 QModbusDataUnit,并通过信号 allRegistersReadCompleted 将结果发射出去,供外部代码进一步处理。

欢迎大家提供优化建议。

#ifndef MODBUSDEVICE_H
#define MODBUSDEVICE_H#include <QObject>
#include <QModbusTcpClient>
#include <QTimer>
#include <QMap>
#include <QVariant>// 定义设备状态的枚举类型,用于表示 Modbus 设备当前的连接状态
enum class DeviceStatus {Disconnected,  // 设备处于断开连接状态Connecting,    // 设备正在尝试连接Connected      // 设备已成功连接
};// ModbusDevice 类继承自 QObject,用于管理 Modbus TCP 客户端的连接和通信
class ModbusDevice : public QObject
{Q_OBJECT
public:// 构造函数,接受一个包含设备属性的 QMap 和可选的父对象指针// properties: 包含设备属性的 QMap,如设备 ID、IP 地址、端口等// parent: 父对象指针,默认为 nullptrexplicit ModbusDevice(const QMap<QString, QVariant>& properties, QObject *parent = nullptr);// 析构函数,负责释放动态分配的资源~ModbusDevice();// 获取设备当前的连接状态DeviceStatus getStatus();// 获取设备的重连尝试次数int deviceReconnectAttempts() const;// 设置设备的属性值// key: 属性的键,如 "deviceID", "ipAddress" 等// value: 属性的值void setProperty(const QString& key, const QVariant& value);// 获取设备的属性值// key: 属性的键// 返回值: 属性的值,如果键不存在则返回默认值QVariant getProperty(const QString& key) const;// 向 Modbus 设备写入寄存器数据// registerType: 寄存器类型,如 QModbusDataUnit::HoldingRegisters// registerAddress: 寄存器的起始地址// values: 要写入的寄存器值的向量// 返回值: 写入操作是否成功bool writeRegisters(QModbusDataUnit::RegisterType registerType, quint16 registerAddress, const QVector<quint16>& values);// 从 Modbus 设备读取寄存器数据// registerType: 寄存器类型// registerAddress: 寄存器的起始地址// count: 要读取的寄存器数量void readRegisters(QModbusDataUnit::RegisterType registerType, quint16 registerAddress, quint16 count);// 尝试连接到 Modbus 设备// 返回值: 连接是否成功bool connectDevice();// 断开与 Modbus 设备的连接void disconnectDevice();// 获取设备的 IDint deviceId() const;// 获取设备的 IP 地址QString deviceAddress() const;// 获取设备的名称QString deviceName() const;// 获取设备的端口号quint16 devicePort() const;signals:// 当设备连接状态发生变化时发出的信号,携带设备 ID// deviceId: 设备的 IDvoid signal_DeviceConnectState(int deviceId);// 当所有寄存器读取完成时发出的信号,携带读取到的数据单元// unit: 包含读取结果的 QModbusDataUnitvoid allRegistersReadCompleted(const QModbusDataUnit& unit);private slots:// 心跳检测槽函数,定期检查设备的连接状态void checkHeartbeat();// 尝试重新连接设备的槽函数,在连接断开时调用void attemptReconnect();// 处理 Modbus 客户端状态变化的槽函数// state: 新的 Modbus 设备状态void handleStateChanged(QModbusDevice::State state);// 处理寄存器读取完成的槽函数void onAllRegistersReadReady();// 设置设备的连接状态,并执行相应的操作// newStatus: 新的设备连接状态void setStatus(DeviceStatus newStatus);private:// 存储设备属性的 QMap,如设备 ID、IP 地址、端口等QMap<QString, QVariant> m_properties;// Modbus TCP 客户端指针,用于与 Modbus 设备进行通信QModbusTcpClient* m_modbusClient;// 心跳检测定时器,定期触发心跳检测QTimer* m_heartbeatTimer;// 重连定时器,在连接断开时定期尝试重新连接QTimer* m_reconnectTimer;// 设备当前的连接状态DeviceStatus m_status;// 记录设备的重连尝试次数int m_reconnectAttempts = 0;// 最大重连尝试次数,超过该次数将停止重连static const int MAX_RECONNECT_ATTEMPTS = 5;
};#endif // MODBUSDEVICE_H    
#include "modbusdevice.h"
#include <QDebug>// 构造函数实现
ModbusDevice::ModbusDevice(const QMap<QString, QVariant>& properties, QObject *parent): QObject(parent),m_properties(properties),m_modbusClient(new QModbusTcpClient(this)),m_status(DeviceStatus::Disconnected)
{// 创建心跳检测定时器,并设置定时器的父对象为当前对象m_heartbeatTimer = new QTimer(this);// 设置心跳检测定时器的间隔为 2000 毫秒(即 2 秒)m_heartbeatTimer->setInterval(2000);// 连接心跳检测定时器的超时信号到 checkHeartbeat 槽函数connect(m_heartbeatTimer, &QTimer::timeout, this, &ModbusDevice::checkHeartbeat);// 创建重连定时器,并设置定时器的父对象为当前对象m_reconnectTimer = new QTimer(this);// 设置重连定时器的间隔为 2000 毫秒(即 2 秒)m_reconnectTimer->setInterval(2000);// 连接重连定时器的超时信号到 attemptReconnect 槽函数connect(m_reconnectTimer, &QTimer::timeout, this, &ModbusDevice::attemptReconnect);// 连接 Modbus 客户端的状态变化信号到 handleStateChanged 槽函数connect(m_modbusClient, &QModbusTcpClient::stateChanged, this, &ModbusDevice::handleStateChanged);
}// 析构函数实现
ModbusDevice::~ModbusDevice()
{// 释放 Modbus 客户端对象的内存delete m_modbusClient;
}// 获取设备状态的函数实现
DeviceStatus ModbusDevice::getStatus()
{// 获取 Modbus 客户端的当前状态QModbusDevice::State state = m_modbusClient->state();if (state == QModbusDevice::ConnectedState) {// 如果 Modbus 客户端已连接,设置设备状态为 ConnectedsetStatus(DeviceStatus::Connected);} else if (state == QModbusDevice::ConnectingState) {// 如果 Modbus 客户端正在连接,设置设备状态为 ConnectingsetStatus(DeviceStatus::Connecting);} else if (state == QModbusDevice::UnconnectedState) {// 如果 Modbus 客户端未连接,设置设备状态为 DisconnectedsetStatus(DeviceStatus::Disconnected);}// 返回设备的当前状态return m_status;
}// 获取设备重连尝试次数的函数实现
int ModbusDevice::deviceReconnectAttempts() const
{// 返回设备的重连尝试次数return m_reconnectAttempts;
}// 设置设备属性的函数实现
void ModbusDevice::setProperty(const QString& key, const QVariant& value)
{// 将属性键值对添加到 m_properties 中m_properties[key] = value;
}// 获取设备属性的函数实现
QVariant ModbusDevice::getProperty(const QString& key) const
{// 从 m_properties 中获取指定键的属性值return m_properties.value(key);
}// 写入寄存器的函数实现
bool ModbusDevice::writeRegisters(QModbusDataUnit::RegisterType registerType, quint16 registerAddress, const QVector<quint16>& values)
{if (m_modbusClient->state() != QModbusDevice::ConnectedState) {// 如果 Modbus 客户端未连接,输出错误信息并返回 falseqDebug() << "Device is not connected. Cannot write registers.";return false;}// 创建一个 Modbus 数据单元,用于写入寄存器QModbusDataUnit writeUnit(registerType, registerAddress, values.size());for (int i = 0; i < values.size(); ++i) {// 将值写入 Modbus 数据单元writeUnit.setValue(i, values[i]);}// 发送写入请求,并获取响应对象QModbusReply* reply = m_modbusClient->sendWriteRequest(writeUnit, m_properties["deviceID"].toInt());if (reply) {// 连接响应对象的完成信号到一个 lambda 函数connect(reply, &QModbusReply::finished, [reply]() {if (reply->error() != QModbusDevice::NoError) {// 如果写入过程中出现错误,输出错误信息qDebug() << "Modbus write registers error:" << reply->errorString();} else {// 如果写入成功,输出成功信息qDebug() << "Modbus write registers success:";}// 释放响应对象的内存reply->deleteLater();});// 返回写入操作成功return true;} else {// 如果发送写入请求失败,输出错误信息并返回 falseqDebug() << "Failed to send write registers request";return false;}
}// 读取寄存器的函数实现
void ModbusDevice::readRegisters(QModbusDataUnit::RegisterType registerType, quint16 registerAddress, quint16 count)
{if (m_modbusClient->state() != QModbusDevice::ConnectedState) {// 如果 Modbus 客户端未连接,输出错误信息并返回qDebug() << "Device is not connected. Cannot read registers.";return;}// 创建一个 Modbus 数据单元,用于读取寄存器QModbusDataUnit readUnit(registerType, registerAddress, count);// 发送读取请求,并获取响应对象QModbusReply* reply = m_modbusClient->sendReadRequest(readUnit, m_properties["deviceID"].toInt());if (reply) {// 连接响应对象的完成信号到 onAllRegistersReadReady 槽函数connect(reply, &QModbusReply::finished, this, &ModbusDevice::onAllRegistersReadReady);} else {// 如果发送读取请求失败,输出错误信息qDebug() << "Failed to send read registers request";}
}// 设置设备状态的函数实现
void ModbusDevice::setStatus(DeviceStatus newStatus)
{if (m_status != newStatus) {// 如果新状态与当前状态不同m_status = newStatus;// 发出设备连接状态变化的信号,携带设备 IDemit signal_DeviceConnectState(m_properties["deviceID"].toInt());if (m_status == DeviceStatus::Connected) {// 如果设备已连接,启动心跳检测定时器m_heartbeatTimer->start();// 重置重连尝试次数m_reconnectAttempts = 0;} else if (m_status == DeviceStatus::Disconnected) {// 如果设备断开连接,停止心跳检测定时器m_heartbeatTimer->stop();if (m_reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {// 如果重连尝试次数未达到最大次数,启动重连定时器m_reconnectTimer->start();}}}
}// 处理 Modbus 客户端状态变化的槽函数实现
void ModbusDevice::handleStateChanged(QModbusDevice::State state)
{switch (state) {case QModbusDevice::UnconnectedState:// 如果 Modbus 客户端未连接,设置设备状态为 DisconnectedsetStatus(DeviceStatus::Disconnected);break;case QModbusDevice::ConnectedState:// 如果 Modbus 客户端已连接,设置设备状态为 ConnectedsetStatus(DeviceStatus::Connected);break;case QModbusDevice::ConnectingState:// 如果 Modbus 客户端正在连接,设置设备状态为 ConnectingsetStatus(DeviceStatus::Connecting);break;default:break;}
}// 心跳检测槽函数实现
void ModbusDevice::checkHeartbeat()
{if (m_modbusClient->state() != QModbusDevice::ConnectedState) {// 如果 Modbus 客户端未连接,处理设备断开连接的情况handleStateChanged(QModbusDevice::UnconnectedState);return;}// 创建一个 Modbus 数据单元,用于读取心跳寄存器QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters, 0, 1);// 发送读取请求,并获取响应对象QModbusReply* reply = m_modbusClient->sendReadRequest(readUnit, m_properties["deviceID"].toInt());if (reply) {// 连接响应对象的完成信号到一个 lambda 函数connect(reply, &QModbusReply::finished, this, [this, reply]() {if (reply->error() != QModbusDevice::NoError) {// 如果读取过程中出现错误,处理设备断开连接的情况handleStateChanged(QModbusDevice::UnconnectedState);}// 释放响应对象的内存reply->deleteLater();});} else {// 如果发送读取请求失败,处理设备断开连接的情况handleStateChanged(QModbusDevice::UnconnectedState);}
}// 尝试重新连接设备的槽函数实现
void ModbusDevice::attemptReconnect()
{if (m_modbusClient->state() != QModbusDevice::ConnectedState) {// 如果 Modbus 客户端未连接,输出重连尝试信息qDebug() << "尝试重连设备 ID:" << m_properties["deviceID"].toInt();if (m_reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {// 如果重连尝试次数未达到最大次数,尝试重新连接设备connectDevice();// 增加重连尝试次数m_reconnectAttempts++;} else {// 如果重连尝试次数达到最大次数,输出重连失败信息并停止重连定时器qDebug() << "设备 ID:" << m_properties["deviceID"].toInt() << " 重连次数达到上限,停止重连";m_reconnectTimer->stop();}}
}// 处理寄存器读取完成的槽函数实现
void ModbusDevice::onAllRegistersReadReady()
{// 获取发送信号的对象,并将其转换为 QModbusReply 指针QModbusReply* reply = qobject_cast<QModbusReply*>(sender());if (!reply) {// 如果转换失败,输出错误信息并返回qDebug() << "Invalid reply object";return;}if (reply->error() != QModbusDevice::NoError) {// 如果读取过程中出现错误,输出错误信息qDebug() << "Modbus read all registers error:" << reply->errorString();} else {// 如果读取成功,获取读取结果并发出所有寄存器读取完成的信号QModbusDataUnit unit = reply->result();emit allRegistersReadCompleted(unit);}// 释放响应对象的内存reply->deleteLater();
}// 连接设备的函数实现
bool ModbusDevice::connectDevice()
{// 从设备属性中获取 IP 地址QString ipAddress = m_properties["ipAddress"].toString();// 从设备属性中获取端口号quint16 port = m_properties["port"].toUInt();// 设置 Modbus 客户端的网络地址参数m_modbusClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, ipAddress);// 设置 Modbus 客户端的网络端口参数m_modbusClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, port);// 尝试连接 Modbus 设备,并获取连接结果bool result = m_modbusClient->connectDevice();if (m_modbusClient->state() == QModbusDevice::ConnectedState) {// 如果 Modbus 客户端已连接,设置设备状态为 ConnectedsetStatus(DeviceStatus::Connected);} else {// 如果 Modbus 客户端未连接,设置设备状态为 DisconnectedsetStatus(DeviceStatus::Disconnected);}// 返回连接结果return result;
}
// 断开设备连接的函数实现
void ModbusDevice::disconnectDevice()
{// 断开 Modbus 客户端与设备的连接m_modbusClient->disconnectDevice();// 设置设备状态为 DisconnectedsetStatus(DeviceStatus::Disconnected);// 停止心跳检测定时器m_heartbeatTimer->stop();// 停止重连定时器m_reconnectTimer->stop();
}
// 获取设备 ID 的函数实现
int ModbusDevice::deviceId() const
{// 从设备属性中获取设备 IDreturn m_properties["deviceID"].toInt();
}
// 获取设备 IP 地址的函数实现
QString ModbusDevice::deviceAddress() const
{// 从设备属性中获取设备 IP 地址return m_properties["ipAddress"].toString();
}
// 获取设备名称的函数实现
QString ModbusDevice::deviceName() const
{// 从设备属性中获取设备名称return m_properties["deviceName"].toString();
}
// 获取设备端口号的函数实现
quint16 ModbusDevice::devicePort() const
{// 从设备属性中获取设备端口号return m_properties["port"].toInt();
}    
#include "modbusdevice.h"
#include <QCoreApplication>
#include <QDebug>int main(int argc, char *argv[])
{// 创建一个 QCoreApplication 对象,用于管理应用程序的生命周期QCoreApplication a(argc, argv);// 创建一个 QMap 对象,用于存储 Modbus 设备的属性QMap<QString, QVariant> properties;// 设置设备的 ID 为 1properties["deviceID"] = 1;// 设置设备的 IP 地址为本地回环地址properties["ipAddress"] = "127.0.0.1";// 设置设备的端口号为 502properties["port"] = 502;// 设置设备的名称为 "TestDevice"properties["deviceName"] = "TestDevice";// 创建一个 ModbusDevice 对象,传入设备属性ModbusDevice device(properties);// 尝试连接 Modbus 设备if (device.connectDevice()) {// 如果连接成功,输出连接成功信息qDebug() << "设备连接成功";// 创建一个 QVector 对象,存储要写入寄存器的值QVector<quint16> values = {1, 2, 3};// 调用 writeRegisters 函数,向设备的保持寄存器写入数据device.writeRegisters(QModbusDataUnit::HoldingRegisters, 0, values);// 调用 readRegisters 函数,从设备的保持寄存器读取数据device.readRegisters(QModbusDataUnit::HoldingRegisters, 0, 3);// 连接 allRegistersReadCompleted 信号到一个 lambda 函数// 当寄存器读取完成时,输出读取到的值QObject::connect(&device, &ModbusDevice::allRegistersReadCompleted, [](const QModbusDataUnit& unit) {qDebug() << "读取寄存器完成,值为:" << unit.values();});} else {// 如果连接失败,输出连接失败信息qDebug() << "设备连接失败";}// 进入应用程序的事件循环,等待事件发生return a.exec();
}    

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

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

相关文章

解决Vmware 运行虚拟机Ubuntu22.04卡顿、终端打字延迟问题

亲测可用 打开虚拟机设置&#xff0c;关闭加速3D图形 &#xff08;应该是显卡驱动的问题&#xff0c;不知道那个版本的驱动不会出现这个问题&#xff0c;所以干脆把加速关了&#xff09;

【网络】Socket套接字

目录 一、端口号 二、初识TCP/UDP协议 三、网络字节序 3.1 概念 3.2 常用API 四、Socket套接字 4.1 概念 4.2 常用API &#xff08;1&#xff09;socket &#xff08;2&#xff09;bind sockaddr结构 &#xff08;3&#xff09;listen &#xff08;4&#xff09;a…

内联函数/函数重载/函数参数缺省

一、内联函数 为了减少函数调用的开销 在函数定义前加“inline”关键字&#xff0c;即可定义内联函数 二、函数重载 1.名字相同 2.参数个数或者参数类型不同 编译器根据调用语句实参的个数和类型判断应该调用哪个函数 三、函数的缺省参数 定义函数的时候可以让最右边的连…

基于神经网络的文本分类的设计与实现

标题:基于神经网络的文本分类的设计与实现 内容:1.摘要 在信息爆炸的时代&#xff0c;大量文本数据的分类处理变得至关重要。本文旨在设计并实现一种基于神经网络的文本分类系统。通过构建合适的神经网络模型&#xff0c;采用公开的文本数据集进行训练和测试。在实验中&#x…

Baklib内容中台的核心定位是什么?

构建企业级知识中枢 在数字化转型趋势下&#xff0c;Baklib内容中台通过构建企业级知识中枢&#xff0c;实现了从碎片化信息到体系化资产的跃迁。其核心能力体现为对多源内容的智能聚合与结构化存储&#xff0c;支持从文档、图片到视频的全格式整合&#xff0c;并通过语义标签…

蓝耘平台API深度剖析:如何高效实现AI应用联动

目录 一、蓝耘平台简介 1.1 蓝耘通义大模型 1.2 蓝耘云计算资源 1.3 蓝耘API与微服务 二、 蓝耘平台应用联动场景 2.1 数据采集与预处理联动 2.2 模型推理与后端服务联动 2.3 跨平台联动 三、蓝耘平台注册体验功能 3.1 注册 3.2 体验蓝耘MaaS平台如何使用海螺AI生成视频…

《大语言模型赋能证券业开发安全:海云安技术方案在上交所专刊发表》

近日&#xff0c;海云安《大语言模型在证券业开发安全领域的探索与实践》技术方案经过上海证券交易所&#xff08;以下简称”上交所“&#xff09;行业专家评审后正式收录于《交易技术前沿——网络安全专刊&#xff08;2025年第1期 总第61期&#xff09;》。 证券信息技术研究…

第三课:Stable Diffusion图生图入门及应用

文章目录 Part01 图生图原理Part02 图生图基本流程Part03 随机种子作用解析Part04 图生图的拓展应用 Part01 图生图原理 当提示词不能足够表达用户需求的时候&#xff0c;加入图片能让AI更好的理解你的想法图片上的像素信息会在加噪和去噪的过程中&#xff0c;作为一种特征反映…

将网络安全和第三方风险管理与业务目标相结合

在网络安全风险领域&#xff0c;我们经常遇到与企业语言不通的问题。这可能导致网络安全风险管理计划得不到支持。当发现网络安全风险时&#xff0c;困难在于以符合组织语言和目标的方式来表达它。 第三方风险属于另一个灰色地带。在组织内部&#xff0c;许多利益相关者&#…

使用Github项目nghttp3的样例学习HTTP/3

文章目录 前言一、HTTP3测试 in Ubuntu1.1. 基本软件1.2. gcc/g1.2.1. Ubuntu221.2.2. Ubuntu201.2.2.1. 必备库1.2.2.1.1. gmp1.2.2.1.2. mpfr1.2.2.1.3. mpc 1.2.2.2. 安装 1.3. libev > 4.11&#xff08;备用&#xff09;1.3.1. 安装1.3.2. 测试 1.4. nghttp31.5. ngtcp2…

uniapp 在app上 字体如何不跟着系统字体大小变

在UniApp开发中&#xff0c;默认情况下App的字体可能会跟随系统字体设置而变化。如果你希望保持固定的字体样式&#xff0c;不随系统字体设置改变&#xff0c;可以采用以下几种方法&#xff1a; 方法一&#xff1a;全局CSS设置 在App.vue的样式中添加以下CSS&#xff1a; /*…

跨域问题的解决方案

一、跨域问题的本质 1.1 同源策略的三要素 浏览器的同源策略&#xff08;Same-Origin Policy&#xff09;要求请求的 协议、域名、端口 完全一致&#xff0c;否则视为跨域&#xff1a; 协议不同&#xff1a;http 与 https域名不同&#xff1a;a.com 与 b.com端口不同&#x…

Linux 上使用 Docker 部署 Kafka 集群

在 Linux 上使用 Docker 部署 Kafka 集群的步骤如下 1. 准备工作 确保已安装&#xff1a; Docker Docker Compose 2. 创建 Docker Compose 文件 (docker-compose.yml) version: 3.8services:zookeeper:image: wurstmeister/zookeepercontainer_name: zookeeperports:- &quo…

【性能优化点滴】odygrd/quill 中一个简单的标记位作用--降低 IO 次数

在 StreamSink 类中&#xff0c;成员变量 _write_occurred 的作用是 跟踪自上次刷新&#xff08;Flush&#xff09;以来是否有写入操作发生&#xff0c;其核心目的是 优化 I/O 性能。以下是详细解析&#xff1a; _write_occurred 的作用 1. 避免不必要的刷新&#xff08;Flush…

Ubuntu Linux安装PyQt5并配置Qt Designer

一 安装 PyQt5 借助 apt 包管理器来安装 PyQt5 及其相关的开发工具&#xff1a; sudo apt install python3-pyqt5 pyqt5-dev-tools 假如报错&#xff0c; You might want to run apt --fix-broken install to correct these. 直接执行&#xff1a; sudo apt --fix-…

2025清华大学:DeepSeek教程全集(PDF+视频精讲,共10份).zip

一、资料列表 第一课&#xff1a;Deepseek基础入门 第二课&#xff1a;DeepSeek赋能职场 第三课&#xff1a;普通人如何抓住DeepSeek红利 第四课&#xff1a;让科研像聊天一样简单 第五课&#xff1a;DeepSeek与AI幻觉 第六课&#xff1a;基于DeepSeek的AI音乐词曲的创造法 第…

容器C++

string容器 string构造函数 #include<iostream> using namespace std; #include<string.h> void test01() {string s1;//默认构造const char* str "hello world";string s2(str);//传入char*cout << "s2" << s2 << endl;s…

【2.项目管理】2.4 Gannt图【甘特图】

甘特图&#xff08;Gantt&#xff09;深度解析与实践指南 &#x1f4ca; 一、甘特图基础模板 项目进度表示例 工作编号工作名称持续时间(月)项目进度&#xff08;周&#xff09;1需求分析3▓▓▓░░░░░░░2设计建模3░▓▓▓░░░░░░3编码开发3.5░░░▓▓▓▓░░…

C++List模拟实现|细节|难点|易错点|全面解析|类型转换|

目录 1.模拟代码全部 2.四大块代码理解 1.最底层&#xff1a;ListNode部分 2.第二层&#xff1a;ListIterator部分 3.第三层&#xff1a;ReserveListIterator部分 4最终层&#xff1a;List 1.模拟代码全部 using namespace std; template<class T> struct ListNode …

【深度学习与实战】2.1、线性回归模型与梯度下降法先导

import numpy as np# 数据准备 X np.array([1, 2, 3]) y np.array([3, 5, 7])# 参数初始化 w0, w1 0, 0 alpha 0.1 n len(X)# 迭代10次 for epoch in range(10):# 计算预测值y_pred w1 * X w0# 计算梯度grad_w0 (1/n) * np.sum(y_pred - y)grad_w1 (1/n) * np.sum((y_…