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

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


http://www.kler.cn/a/556694.html

相关文章:

  • SQLMesh 系列教程6- 详解 Python 模型
  • 物联网与大数据:揭秘万物互联的新纪元
  • (网络安全)渗透测试
  • 想象一个AI保姆机器人使用场景分析
  • 如何使用 JavaScript 模拟 Docker 中的 UnionFS 技术:从容器到文件系统的映射
  • 8.python文件
  • 纳米科技新突破:AbMole助力探索主动脉夹层的基因密码
  • Java 同步锁性能的最佳实践:从理论到实践的完整指南
  • Java中JDK、JRE,JVM之间的关系
  • 【全栈】SprintBoot+vue3迷你商城(12)
  • 企业商业秘密百问百答之五十三【商业秘密转让】
  • 【目标检测】【PANet】Path Aggregation Network for Instance Segmentation
  • 九联UNT403AS_晶晨S905L3S芯片_2+8G_安卓9.0_卡刷固件包
  • R语言安装生物信息数据库包
  • 一篇搞懂vue3中如何使用ref、reactive实现响应式数据
  • Ubuntu22.04.6如何固定ip地址
  • Webpack打包优化
  • 蓝桥杯 2.基础算法
  • 【中间件开发】kafka使用场景与设计原理
  • Selenium实战案例2:东方财富网股吧评论爬取