【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.pswp.cn/web/70576.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/70576.shtml
英文地址,請注明出處:http://en.pswp.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; 有時候不太喜歡紙質題庫…