【QT 网络编程】HTTP协议(二)

文章目录

  • 🌟1.概述
  • 🌟2.代码结构概览
  • 🌟3.代码解析
    • 🌸Http_Api_Manager - API管理类
    • 🌸Http_Request_Manager- HTTP请求管理类
    • 🌸ThreadPool - 线程池
    • 🌸TestWindow- 测试类
  • 🌟4.运行效果
  • 🌟5.总结

🌟1.概述

本文将基于Qt框架,讲解如何实现一个高效的HTTP客户端,并分析其核心代码实现原理。
Qt 提供了 QNetworkAccessManager 作为HTTP请求的核心组件,同时结合 QNetworkRequestQNetworkReply,可以完成基本的HTTP通信。此外,为了提高并发处理能力,我们还会结合 ThreadPool 对请求的响应进行异步管理。

🌟2.代码结构概览

该HTTP客户端主要由以下几个核心组件组成:

  • Http_Api_Manager:对外提供API调用的管理类,封装业务逻辑。
  • Http_Request_Manager:具体负责发送HTTP请求。
  • ThreadPool:线程池,用于异步处理请求,提高并发能力。
  • TestWindow:测试类,用于模拟多次请求,测试出http请求的性能数据。

🌟3.代码解析

🌸Http_Api_Manager - API管理类

class Http_Api_Manager : public QObject
{Q_OBJECTstruct ApiResponse {// 通用响应数据结构解析,根据自定义响应报文解析int code;QString message;QJsonValue data;static ApiResponse fromJson(const QJsonDocument& doc);};public:static Http_Api_Manager* getInstance();// 测试API方法,可以考虑抽象出基类请求,外部调用公共接口,内部进行API类型区分void updateAllVoteItem(const QString &userGuid, const QString &conferenceGuid, int pageNum, int pageSize, int requestIndex = -1);signals://响应处理完毕信号void allVoteItemUpdated(bool success,int index = 0);void requestError(const QString& errorMessage);private:static Http_Api_Manager* instance;//单例std::unique_ptr<ThreadPool> m_threadPool;//线程池class Private;//声明私有类,将具体实现隐藏std::unique_ptr<Private> d;//指向私有实现的指针private:explicit Http_Api_Manager(QObject *parent = nullptr);~Http_Api_Manager();// 基础JSON解析函数,将业务响应处理函数作为传参回调void processResponse(const QByteArray &response,std::function<void(const QJsonDocument&)> handler);                     // 业务响应处理函数void processVoteListResponse(const QJsonDocument& doc,const int &requestIndex = -1);
};

将API管理类实现为单例,方便全局调用以及对HTTP请求的管理。新增接口只需实现具体的请求函数、请求响应的业务处理函数以及处理完毕的信号函数。 对于响应的业务处理会放入子线程异步执行。

//具体的API请求函数
void Http_Api_Manager::updateAllVoteItem(const QString &userGuid, const QString &conferenceGuid, int pageNum, int pageSize, int requestIndex)
{//查询传参QMap<QString, QString> queryParams = { {"guid", conferenceGuid}, {"pageNum", QString::number(pageNum)}, {"pageSize", QString::number(pageSize)} };//封装一个请求任务Http_Request_Task task(Http_Request_Task::GET, "/api/client/voting/findVotingStatisticList", queryParams);//调用Http_Request_Manager的公共接口,并传入响应处理回调函数Http_Request_Manager::getInstance()->sendRequest(task, [this, requestIndex](const QByteArray& response) {m_threadPool->enqueue([this, response, requestIndex]() {processResponse(response, std::bind(&Http_Api_Manager::processVoteListResponse, this, std::placeholders::_1, requestIndex));});});
}
//具体的响应业务处理函数
void Http_Api_Manager::processVoteListResponse(const QJsonDocument& doc, const int &requestIndex) {ApiResponse response = ApiResponse::fromJson(doc);if (response.code != 0) {emit requestError(response.message);return;}if (response.message.contains("Success")) {QJsonArray voteItems = response.data.toArray();// 在这里可以进行具体的数据转换和处理if(requestIndex!=-1)emit allVoteItemUpdated(true,requestIndex);elseemit allVoteItemUpdated(true);} else {emit requestError("Invalid vote list data format");}
}

🌸Http_Request_Manager- HTTP请求管理类

class Http_Request_Task {//请求任务封装类
public:enum RequestType {//请求类型GET,POST,POST_FILE};Http_Request_Task(RequestType type, const QString& api,const QMap<QString, QString>& params = {}, const QByteArray& data = QByteArray()): m_tType(type), m_sApi(api), m_mRequestParams(params), m_bData(data) {}//构造RequestType type() const { return m_tType; }QString api() const { return m_sApi; }QMap<QString, QString> params() const { return m_mRequestParams; }QByteArray data() const { return m_bData; }private:RequestType m_tType;//请求类型 GET|POST|POST_FILEQString m_sApi;//api接口QMap<QString, QString> m_mRequestParams;//请求传参QByteArray m_bData;//header传参
};typedef std::function<void(const QByteArray&)> ResponseCallback;//声明请求回调类型
class Http_Request_Manager : public QObject//请求管理类
{Q_OBJECT
public:static Http_Request_Manager* getInstance(QObject *parent=nullptr);//单例构造static QNetworkAccessManager* networkAccessManager() {return getInstance()->m_pSharedNAM;}void sendRequest(const Http_Request_Task& task, ResponseCallback callback);//发送请求,传入task以及处理函数指针void setServerAddress(const QString &newServerAddress);//设置服务器地址private:static Http_Request_Manager* instance;//单例QNetworkAccessManager *m_pSharedNAM = nullptr;//唯一网络处理实例QString m_sServerAddress;//服务器地址QMap<QNetworkReply*, ResponseCallback> m_mReplyCallbacks;//网络请求与回调的映射private:Http_Request_Manager(QObject *parent=nullptr);~Http_Request_Manager();QUrl constructURL(const QString& api, const QUrlQuery& query);void setSSLConfig();QNetworkReply* sendGetRequest(const QNetworkRequest& request);QNetworkReply* sendPostRequest(const QNetworkRequest& request, const QByteArray& data);QNetworkReply* sendFileRequest(const QNetworkRequest& request, const QString& filePath);
};

将HTTP请求管理类实现为单例,为了复用QNetworkAccessManager实例。QNetworkAccessManager内部维护连接池以及线程池默认异步调用,如果创建多个实例,tcp连接可能无法复用。

//对外暴露功能请求接口,内部实现请求区分
void Http_Request_Manager::sendRequest(const Http_Request_Task &task, ResponseCallback callback)
{QUrlQuery query;for (auto it = task.params().constBegin(); it != task.params().constEnd(); ++it) {query.addQueryItem(it.key(), it.value());}QUrl url = constructURL(task.api(), query);QNetworkRequest request(url);//启用 HTTP/2 或 Pipelining,提高并发能力。//默认http1,Qt的HTTP连接池限制同一主机的最大并发连接数。request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true);request.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);QNetworkReply* reply = nullptr;switch (task.type()) {case Http_Request_Task::GET:reply = sendGetRequest(request);break;case Http_Request_Task::POST:reply = sendPostRequest(request, task.data());break;case Http_Request_Task::POST_FILE:reply = sendFileRequest(request, QString(task.data()));break;}if (reply) {m_mReplyCallbacks.insert(reply, callback);connect(reply, &QNetworkReply::finished, this, [this, reply]() {if (reply->error() == QNetworkReply::NoError) {QByteArray responseData = reply->readAll();if (m_mReplyCallbacks.contains(reply)) {m_mReplyCallbacks[reply](responseData);m_mReplyCallbacks.remove(reply);}}reply->deleteLater();});}
}

🌸ThreadPool - 线程池

class ThreadPool {
public:ThreadPool(size_t);template<class F, class... Args>auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type>;~ThreadPool();
};

C++11语法的线程池实现在Github是开源的,非常牛杯一的代码。

🌸TestWindow- 测试类

class TestWindow : public QObject
{Q_OBJECTstruct RequestMetrics {qint64 requestStartTime; // 请求开始时间qint64 requestEndTime;   // 请求结束时间qint64 processingTime;  // 请求耗时bool success;           // 是否成功};
public:explicit TestWindow(QObject *parent = nullptr);void testRequest(int loopCount = 1); // 添加循环次数参数signals:void testCompleted(int totalRequests, int successfulRequests, int failedRequests, qint64 totalTime, const QList<RequestMetrics>& metrics);private:QElapsedTimer m_testTimer;int m_requestCount;int m_concurrentRequests;int m_completedRequests;int m_successfulRequests;int m_failedRequests;QList<RequestMetrics> m_requestMetrics; // 存储所有请求的执行情况private:QString formatTime(const qint64 &timestamp);
};

在调用类中,只需要初始化Http_Request_Manager服务器地址,发出具体请求以及连接响应处理信号。

Http_Request_Manager::getInstance()->setServerAddress("192.168.42.101");//设置服务器地址auto* apiManager = Http_Api_Manager::getInstance();
connect(apiManager, &Http_Api_Manager::allVoteItemUpdated, this, [=](bool success, int requestIndex) {qint64 endTime = QDateTime::currentMSecsSinceEpoch();// 根据请求索引获取对应的 RequestMetrics 对象if (requestIndex >= 0 && requestIndex < m_requestMetrics.size()) {RequestMetrics& metrics = m_requestMetrics[requestIndex];metrics.requestEndTime = endTime;metrics.processingTime = endTime - metrics.requestStartTime;metrics.success = success;m_completedRequests++;m_concurrentRequests--;if (success) {m_successfulRequests++;} else {m_failedRequests++;}LogDebug << "Request" << (requestIndex + 1) << "completed. Success:" << success<< "Start Time:" << metrics.requestStartTime<< "End Time:" << metrics.requestEndTime<< "Processing Time:" << metrics.processingTime << "ms";// 如果所有请求完成,发出测试完成信号if (m_completedRequests == m_requestCount) {qint64 totalTime = m_testTimer.elapsed();emit testCompleted(m_requestCount, m_successfulRequests, m_failedRequests, totalTime, m_requestMetrics);}}});
void TestWindow::testRequest(int loopCount)
{auto* apiManager = Http_Api_Manager::getInstance();m_testTimer.start(); // 开始计时m_requestCount = loopCount; // 设置总请求数m_completedRequests = 0; // 重置完成请求数m_successfulRequests = 0; // 重置成功请求数m_failedRequests = 0; // 重置失败请求数m_requestMetrics.clear(); // 清空之前的请求记录for (int i = 0; i < loopCount; ++i) {m_concurrentRequests++;RequestMetrics metrics;metrics.requestStartTime = QDateTime::currentMSecsSinceEpoch(); // 记录请求开始时间m_requestMetrics.append(metrics); // 存储当前请求的开始时间// 发送请求
//        apiManager->updateAllVoteItem("e7cb3697-74ac-4b48-a472-dab5637e7968", "76050537-f63e-41db-a060-949d9d9def52", 1, 100,i);apiManager->updateAllVoteItem("e7cb3697-74ac-4b48-a472-dab5637e7968", "e92647e1-e81a-4c14-9678-10275a81f9c7", 1, 100,i);}
}

🌟4.运行效果

  • 由于启用HTTP/2,对同一服务器的理论并发请求数可达几十到上百。
  • 实际并发数会受到服务器端配置、网络带宽等因素限制。
  • 保守估计稳定并发在20-30个请求是可行的。
  • 实际测试,100个并发请求,每个响应耗时是逐步递增,总体耗时400ms左右。
  • 线程池的最大线程数量应该根据cpu的内核数量来设置,大概将线程数设置为cpu(p核+e核)*2左右最合适。

🌟5.总结

本文介绍了基于Qt实现的HTTP客户端,包括API管理、HTTP请求处理、线程池以及测试组件。该实现具有高效的异步处理能力,适用于高并发场景。

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

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

相关文章

保姆级! 本地部署DeepSeek-R1大模型 安装Ollama Api 后,Postman本地调用 deepseek

要在Postman中访问Ollama API并调用DeepSeek模型,你需要遵循以下步骤。首先,确保你有一个有效的Ollama服务器实例运行中,并且DeepSeek模型已经被加载。 可以参考我的这篇博客 保姆级!使用Ollama本地部署DeepSeek-R1大模型 并java通过api 调用 具体的代码实现参考我这个博…

在PHP Web开发中,实现异步处理有几种常见方式的优缺点,以及最佳实践推荐方法

1. 消息队列 使用消息队列&#xff08;如RabbitMQ、Beanstalkd、Redis&#xff09;将任务放入队列&#xff0c;由后台进程异步处理。 优点&#xff1a; 任务持久化&#xff0c;系统崩溃后任务不丢失。 支持分布式处理&#xff0c;扩展性强。 实现步骤&#xff1a; 安装消息…

算法15--BFS

BFS 原理经典例题解决FloodFill 算法[733. 图像渲染](https://leetcode.cn/problems/flood-fill/description/)[200. 岛屿数量](https://leetcode.cn/problems/number-of-islands/description/)[695. 岛屿的最大面积](https://leetcode.cn/problems/max-area-of-island/descrip…

网络空间安全(2)应用程序安全

前言 应用程序安全&#xff08;Application Security&#xff0c;简称AppSec&#xff09;是一个综合性的概念&#xff0c;它涵盖了应用程序从开发到部署&#xff0c;再到后续维护的整个过程中的安全措施。 一、定义与重要性 定义&#xff1a;应用程序安全是指识别和修复应用程序…

Plantsimulation中机器人怎么通过阻塞角度设置旋转135°

创建一个这样的简单模型。 检查PickAndPlace的角度表。源位于180的角位置&#xff0c;而物料终结位于90的角位置。“返回默认位置”选项未被勾选。源每分钟生成一个零件。启动模拟时&#xff0c;Plant Simulation会选择两个位置之间的最短路径。示例中的机器人无法绕135的角位…

Fisher信息矩阵(Fisher Information Matrix, FIM)与自然梯度下降:机器学习中的优化利器

Fisher信息矩阵与自然梯度下降&#xff1a;机器学习中的优化利器 在机器学习尤其是深度学习中&#xff0c;优化模型参数是一个核心任务。我们通常依赖梯度下降&#xff08;Gradient Descent&#xff09;来调整参数&#xff0c;但普通的梯度下降有时会显得“笨拙”&#xff0c;…

Spring Boot集成Swagger API文档:傻瓜式零基础教程

Springfox Swagger 是一个用于构建基于 Spring Boot 的 RESTful API 文档的开源工具。它通过使用注解来描述 API 端点&#xff0c;自动生成易于阅读和理解的 API 文档。Springfox 通过在运行时检查应用程序&#xff0c;基于 Spring 配置、类结构和各种编译时 Java 注释来推断 A…

接口测试基础 --- 什么是接口测试及其测试流程?

接口测试是软件测试中的一个重要部分&#xff0c;它主要用于验证和评估不同软件组件之间的通信和交互。接口测试的目标是确保不同的系统、模块或组件能够相互连接并正常工作。 接口测试流程可以分为以下几个步骤&#xff1a; 1.需求分析&#xff1a;首先&#xff0c;需要仔细…

kafka-集群缩容

一. 简述&#xff1a; 当业务增加时&#xff0c;服务瓶颈&#xff0c;我们需要进行扩容。当业务量下降时&#xff0c;为成本考虑。自然也会涉及到缩容。假设集群有 15 台机器&#xff0c;预计缩到 10 台机器&#xff0c;那么需要做 5 次缩容操作&#xff0c;每次将一个节点下线…

Spring Boot 概要(官网文档解读)

Spring Boot 概述 Spring Boot 是一个高效构建 Spring 生产级应用的脚手架工具&#xff0c;它简化了基于 Spring 框架的开发过程。 Spring Boot 也是一个“构件组装门户”&#xff0c;何为构件组装门户呢&#xff1f;所谓的“构件组装门户”指的是一个对外提供的Web平台&#x…

Linux 命令大全完整版(12)

Linux 命令大全 5. 文件管理命令 ln(link) 功能说明&#xff1a;连接文件或目录。语  法&#xff1a;ln [-bdfinsv][-S <字尾备份字符串>][-V <备份方式>][--help][--version][源文件或目录][目标文件或目录] 或 ln [-bdfinsv][-S <字尾备份字符串>][-V…

遗传算法初探

组成要素 编码 分为二进制编码、实数编码和顺序编码 初始种群的产生 分为随机方法、基于反向学习优化的种群产生。 基于反向学习优化的种群其思想是先随机生成一个种群P(N)&#xff0c;然后按照反向学习方法生成新的种群OP(N),合并两个种群&#xff0c;得到一个新的种群S(N…

【算法】堆

堆 heap&#xff0c;一棵完全二叉树&#xff0c;使用数组实现的&#xff0c;但具备完全二叉树的一些性质。一般总是满足以下性质&#xff1a; 堆中某个节点的值总是不大于或不小于其父节点的值&#xff1b;堆总是一棵完全二叉树。&#xff08;即除了最底层&#xff0c;其他层…

C/C++高性能Web开发框架全解析:2025技术选型指南

一、工业级框架深度解析&#xff08;附性能实测&#xff09; 1. Drogon v2.1&#xff1a;异步框架性能王者 核心架构&#xff1a; Reactor 非阻塞I/O线程池&#xff08;参考Nginx模型&#xff09; 协程实现&#xff1a;基于Boost.Coroutine2&#xff08;兼容C11&#xff09;…

使用PHP接入纯真IP库:实现IP地址地理位置查询

引言 在日常开发中,我们经常需要根据用户的IP地址获取其地理位置信息,例如国家、省份、城市等。纯真IP库(QQWry)是一个常用的IP地址数据库,提供了丰富的IP地址与地理位置的映射关系。本文将介绍如何使用PHP接入纯真IP库,并通过一个完整的案例演示如何实现IP地址的地理位…

Django ORM 的常用字段类型、外键关联的跨表引用技巧,以及 `_` 和 `__` 的使用场景

一、Django ORM 常用字段类型 1. 基础字段类型 字段类型说明示例CharField字符串字段&#xff0c;必须指定 max_lengthname models.CharField(max_length50)IntegerField整数字段age models.IntegerField()BooleanField布尔值字段is_active models.BooleanField()DateFiel…

java递归求自然数列的前n项和

概述 实现 /*** 数列 1 2 3 ... n ...* 递归求数列的前n项和* param n* return*/private static long calSum(long n){if (n1) return 1;else {return ncalSum(n-1); // 前n项的和 即第n项的值前n-1项的和}}测试用例 public static void main(String[] args) {long res1 cal…

【Golang 面试题】每日 3 题(六十五)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/UWz06 &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏…

16、Python面试题解析:python中的浅拷贝和深拷贝

在 Python 中&#xff0c;浅拷贝&#xff08;Shallow Copy&#xff09; 和 深拷贝&#xff08;Deep Copy&#xff09; 是处理对象复制的两种重要机制&#xff0c;它们的区别主要体现在对嵌套对象的处理方式上。以下是详细解析&#xff1a; 1. 浅拷贝&#xff08;Shallow Copy&a…

【Godot4.3】题目与答案解析合并器

免责申明 本文和工具截图中涉及题库和题目&#xff0c;均为本人自学使用&#xff0c;并未有商业和传播企图。如有侵害&#xff0c;联系删改。 概述 笔者本人医学专业从业人员&#xff0c;编程只是业余爱好。在自己的专业应考学习过程当中&#xff1a; 有时候不太喜欢纸质题库…