HTTP 失败重试(重发)方案
在 Qt 网络开发中,使用 QNetworkAccessManager
进行 HTTP 请求时,可能会遇到网络超时、服务器错误等情况。为了提高请求的可靠性,可以实现 HTTP 失败重试(重发) 机制。下面介绍几种常见的 失败重发方案:
单请求
1. 基本重试策略
适用于: 短暂的网络抖动、服务器瞬时不可用等情况。
实现思路:
- 监听
QNetworkReply
的finished()
信号,检查error()
是否为失败状态。 - 如果失败,等待一段时间后重新发送请求。
- 设定 最大重试次数,避免无限循环重试。
-
#include <QCoreApplication> #include <QNetworkAccessManager> #include <QNetworkRequest> #include <QNetworkReply> #include <QTimer> #include <QDebug> class HttpRetryHandler : public QObject { Q_OBJECT public: HttpRetryHandler(QObject *parent = nullptr) : QObject(parent), networkManager(new QNetworkAccessManager(this)), retryCount(0) { connect(networkManager, &QNetworkAccessManager::finished, this, &HttpRetryHandler::onReplyFinished); } void sendRequest() { QNetworkRequest request(QUrl("https://example.com/api")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); reply = networkManager->get(request); connect(reply, &QNetworkReply::finished, this, &HttpRetryHandler::onReplyFinished); } private slots: void onReplyFinished() { if (reply->error() == QNetworkReply::NoError) { qDebug() << "Request successful:" << reply->readAll(); reply->deleteLater(); } else { qDebug() << "Request failed:" << reply->errorString(); if (retryCount < maxRetries) { retryCount++; qDebug() << "Retrying..." << retryCount; QTimer::singleShot(retryInterval, this, &HttpRetryHandler::sendRequest); } else { qDebug() << "Max retries reached. Giving up."; } } } private: QNetworkAccessManager *networkManager; QNetworkReply *reply; int retryCount; const int maxRetries = 3; // 最大重试次数 const int retryInterval = 2000; // 失败后等待 2 秒再重试 }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); HttpRetryHandler handler; handler.sendRequest(); return a.exec(); } #include "main.moc"
关键点
- 设置最大重试次数 (
maxRetries
),避免无限重试。 - 使用
QTimer::singleShot()
延迟重试,避免立即重试导致服务器压力增大。
2. 指数退避(Exponential Backoff)重试
适用于: API 速率限制、网络负载较高时的自动恢复。
实现思路:
- 每次重试时,等待时间 指数增长(如
2^retryCount
)。 - 可以设置一个 最大等待时间,避免等待过长。
- 适用于 API 限流(如 HTTP 429 Too Many Requests)或 服务器负载高的情况。
void retryWithBackoff() {
if (retryCount < maxRetries) {
int delay = qMin(initialRetryInterval * (1 << retryCount), maxRetryInterval);
qDebug() << "Retrying in" << delay << "ms...";
QTimer::singleShot(delay, this, &HttpRetryHandler::sendRequest);
retryCount++;
} else {
qDebug() << "Max retries reached. Stopping.";
}
}
关键点
- 指数增长等待时间:
delay = initialRetryInterval * (1 << retryCount)
- 避免等待过长:使用
qMin()
限制最大重试间隔。
3. 仅对特定 HTTP 状态码重试
适用于:
- 服务器错误(HTTP 5xx,如
500
、502
、503
)。 - 速率限制(HTTP
429 Too Many Requests
)。
void onReplyFinished() {
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (reply->error() == QNetworkReply::NoError) {
qDebug() << "Success:" << reply->readAll();
} else if (statusCode == 500 || statusCode == 502 || statusCode == 503 || statusCode == 429) {
qDebug() << "Server error, retrying...";
retryWithBackoff();
} else {
qDebug() << "Request failed permanently:" << reply->errorString();
}
}
关键点
- 避免对所有错误重试,只对 5xx/429 进行重试,减少无效请求。
4. 结合 QNetworkReply::redirected()
处理重定向
适用于: 遇到 301/302/307 重定向 时自动跟随新 URL。
void onReplyFinished() {
QVariant redirectTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
if (!redirectTarget.isNull()) {
QUrl newUrl = redirectTarget.toUrl();
qDebug() << "Redirected to:" << newUrl;
request.setUrl(newUrl);
sendRequest();
return;
}
}
关键点
- 检查
RedirectionTargetAttribute
并重新发起请求。
总结
方案 | 适用情况 | 优缺点 |
---|---|---|
基本重试 | 网络波动、短暂的服务器异常 | 简单有效,但可能导致过多请求 |
指数退避 | API 限流、服务器负载高 | 避免频繁请求,适应性更强 |
特定状态码重试 | 5xx/429 错误 | 只在必要时重试,减少无效请求 |
自动重定向 | 301/302/307 响应 | 处理 URL 变更,防止访问失败 |
在实际开发中,可以 结合多种策略:
- 对
5xx/429
进行指数退避重试。 - 对
301/302
自动重定向。 - 对不可恢复错误(如
403/404
)直接放弃。
多请求
如果有多个失败请求需要重试,可以使用 队列管理 机制来处理所有失败的请求,而不是单独重试每个请求。这可以确保 多个请求按顺序重试,并且不会让服务器负担过重。或者可以让请求并行。
方案 1:使用队列逐个重试
- 适用于: 不希望所有请求同时重试,逐个处理失败请求,降低服务器压力。
实现思路:
- 维护一个
QQueue<QNetworkRequest>
队列,存储失败的请求。 - 失败请求会进入队列,并按照 FIFO(先进先出)顺序依次重试。
- 使用
QTimer
控制指数退避重试时间,依次出队并重试。 - 如果重试成功,从队列中移除请求,否则增加重试次数,重新排队。
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QTimer>
#include <QQueue>
#include <QDebug>
#include <QRandomGenerator>
class HttpRetryHandler : public QObject {
Q_OBJECT
public:
HttpRetryHandler(QObject *parent = nullptr)
: QObject(parent), networkManager(new QNetworkAccessManager(this)) {
connect(networkManager, &QNetworkAccessManager::finished, this, &HttpRetryHandler::onReplyFinished);
}
void sendRequest(const QUrl &url) {
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QNetworkReply *reply = networkManager->get(request);
requestMap.insert(reply, {request, 0}); // 记录请求和当前重试次数
connect(reply, &QNetworkReply::finished, this, &HttpRetryHandler::onReplyFinished);
}
private slots:
void onReplyFinished() {
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
if (!reply) return;
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
auto it = requestMap.find(reply);
if (reply->error() == QNetworkReply::NoError) {
qDebug() << "Request successful: " << reply->readAll();
requestMap.remove(reply);
} else if (statusCode == 500 || statusCode == 502 || statusCode == 503 || statusCode == 429) {
// 服务器错误或限流,加入重试队列
qDebug() << "Server error" << statusCode << ", adding request to retry queue...";
retryQueue.enqueue(it.value());
processRetryQueue(); // 处理队列
} else {
qDebug() << "Request failed permanently: " << reply->errorString();
}
reply->deleteLater();
}
void processRetryQueue() {
if (retryQueue.isEmpty() || retrying) return;
retrying = true;
RequestData requestData = retryQueue.dequeue();
if (requestData.retryCount < maxRetries) {
int delay = qMin(initialRetryInterval * (1 << requestData.retryCount) + getJitter(), maxRetryInterval);
qDebug() << "Retrying request in" << delay << "ms...";
QTimer::singleShot(delay, this, [=]() {
sendRequest(requestData.request.url());
retrying = false;
processRetryQueue(); // 继续处理下一个请求
});
requestData.retryCount++;
} else {
qDebug() << "Max retries reached for request: " << requestData.request.url();
retrying = false;
}
}
private:
struct RequestData {
QNetworkRequest request;
int retryCount;
};
QNetworkAccessManager *networkManager;
QMap<QNetworkReply *, RequestData> requestMap;
QQueue<RequestData> retryQueue;
bool retrying = false;
const int maxRetries = 5; // 最大重试次数
const int initialRetryInterval = 1000; // 初始重试间隔 1 秒
const int maxRetryInterval = 16000; // 最大重试间隔 16 秒
int getJitter() { return QRandomGenerator::global()->bounded(500); } // 额外随机延迟 0~500ms
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
HttpRetryHandler handler;
// 发送多个请求
handler.sendRequest(QUrl("https://example.com/api1"));
handler.sendRequest(QUrl("https://example.com/api2"));
handler.sendRequest(QUrl("https://example.com/api3"));
return a.exec();
}
#include "main.moc"
关键点
(1) 多个请求失败后的处理
- 使用
QMap<QNetworkReply *, RequestData>
记录请求信息 QQueue<RequestData>
存储失败的请求processRetryQueue()
依次从队列取出请求并重试
(2) 防止所有请求同时重试
- 使用
bool retrying
确保一次只处理一个重试请求 - 指数退避 (
2^retryCount
) 并增加随机抖动
**(3) 重试失败的请求
- 如果达到
maxRetries
,则放弃重试 - 否则等待
delay
时间后重试
方案 2:并行重试多个请求
如果你不想让请求 顺序重试,而是 并行重试多个请求,可以 使用多个 QTimer 并行调度,同时让多个请求进行指数退避重试。
void processRetryQueue() {
while (!retryQueue.isEmpty()) {
RequestData requestData = retryQueue.dequeue();
if (requestData.retryCount < maxRetries) {
int delay = qMin(initialRetryInterval * (1 << requestData.retryCount) + getJitter(), maxRetryInterval);
qDebug() << "Retrying request to " << requestData.request.url() << " in " << delay << "ms...";
QTimer::singleShot(delay, this, [=]() {
sendRequest(requestData.request.url());
});
requestData.retryCount++;
} else {
qDebug() << "Max retries reached for request: " << requestData.request.url();
}
}
}
区别:
- 多个请求可以同时重试(不会等待上一个重试完成)。
- 所有请求仍然使用指数退避时间控制频率。
5. 结论
方案 | 优点 | 缺点 |
---|---|---|
顺序重试(队列模式) | 减少服务器压力、保证请求顺序 | 可能导致某些请求等待较久 |
并行重试(多定时器) | 提高吞吐量,适合高并发 | 可能让服务器短时间内收到大量重试请求 |
注:本人也在学习中,如果有错误,请指出!!!
原文地址:https://blog.csdn.net/m0_62573048/article/details/146362122
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/596460.html 如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/596460.html 如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!