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

【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_OBJECT
    struct 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);
        else
            emit 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_FILE
    QString 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_OBJECT
    struct 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请求处理、线程池以及测试组件。该实现具有高效的异步处理能力,适用于高并发场景。

相关文章:

  • 【Gin】| 框架源码解析 :路由详解
  • jQuery UI 主题:设计、定制与优化指南
  • 【Python 语法】常用 Python 内置函数
  • 【Python爬虫(39)】掌控全局:分布式爬虫的任务管理与监控之道
  • 单元测试的策略有哪些,主要包括什么?
  • 【C/C++】分隔链表 (leetcode T86)
  • DeepSeek写俄罗斯方块手机小游戏
  • 图论 之 最小生成树
  • 深入解析Spring Cloud Config:构建高可用分布式配置中心
  • SpringBoot 新特性
  • C++ 设计模式-模板方法模式
  • 【ROS2】卡尔曼滤波学习:概念、数学推导和C++实现方法
  • 【Linux探索学习】第三十弹——线程互斥与同步(上):深入理解线程保证安全的机制
  • CPU、SOC、MPU、MCU--详细分析四者的区别
  • 【Springboot3】Springboot3 搭建RocketMQ 最简单案例
  • UE5 GamePlay 知识点
  • Powershell Install deepseek
  • 【JavaEE进阶】Spring MVC(4)-图书管理系统案例
  • Python 下载/安装
  • [免费]微信小程序智能商城系统(uniapp+Springboot后端+vue管理端)【论文+源码+SQL脚本】
  • 企业网站设计调查问卷/深圳市昊客网络科技有限公司
  • 做兼职一般去哪个网站好/打开百度一下网页版
  • 青岛网站建设/新闻 最新消息
  • 规划和设计一个网站/单页面网站如何优化
  • 东莞疫情2023/专业seo培训学校
  • 网站推广公司有哪些/免费技能培训网