【QT 网络编程】HTTP协议(二)
文章目录
- 🌟1.概述
- 🌟2.代码结构概览
- 🌟3.代码解析
- 🌸Http_Api_Manager - API管理类
- 🌸Http_Request_Manager- HTTP请求管理类
- 🌸ThreadPool - 线程池
- 🌸TestWindow- 测试类
- 🌟4.运行效果
- 🌟5.总结
🌟1.概述
本文将基于Qt框架,讲解如何实现一个高效的HTTP客户端,并分析其核心代码实现原理。
Qt 提供了 QNetworkAccessManager 作为HTTP请求的核心组件,同时结合 QNetworkRequest 和 QNetworkReply,可以完成基本的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 ×tamp);
};
在调用类中,只需要初始化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请求处理、线程池以及测试组件。该实现具有高效的异步处理能力,适用于高并发场景。