Qt之處理QNetworkAccessManager網路連線超時
簡述
在網路操作中,經常會由於各種原因引起網路連線超時,究竟何為網路連線超時?
網路連線超時:在程式預設的等待時間內沒有得到伺服器的響應
超時原因
引起網路連線超時的原因很多,下面,列舉一些常見的原因:
- 網路斷開,不過經常顯示無法連線
- 網路阻塞,導致你不能在程式預設等待時間內得到回覆資料包
- 網路不穩定,網路無法完整傳送伺服器資訊
- 系統問題,系統資源過低,無法為程式提供足夠的資源處理伺服器資訊
- 裝置不穩定,如網線鬆動、介面沒插好等等
- 網路註冊時系統繁忙,無法迴應
- 網速過慢,如 使用 BT 多執行緒下載,線上收看視訊等大量佔用頻寬的軟體 ,若使用共享頻寬還要防範他人惡意佔用頻寬
- 計算機感染了惡意軟體,計算機病毒,計算機木馬等
Qt 中的網路連線超時
在 Qt 中,關於 QNetworkAccessManager、QNetworkRequest 和 QNetworkReply 的文件中,找到了有關超時相關的錯誤 QNetworkReply::NetworkError。
常量 QNetworkReply::TimeoutError:
the connection to the remote server timed out
瞬間欣喜若狂,既然有超時錯誤,必然有設定超時的介面吧!遺憾,遺憾,遺憾。。。重要的事情說 3 遍,翻遍了官方文件,能和超時扯上關係的就這麼一個簡單的常量說明(當然還有 QNetworkReply::ProxyTimeoutError)。
這種情況下,我們只能自己去處理超時了。
如何處理超時
解決思路:
- 使用 QTimer 啟動一個單次定時器,並設定超時時間。
- 在事件迴圈退出之後,判斷定時器的狀態,如果是啟用狀態,證明請求已經完成;否則,說明超時。
來看一個簡單的例子 - 獲取 Qt 官網 網頁內容:
QTimer timer;
timer.setInterval(30000); // 設定超時時間 30 秒
timer.setSingleShot(true); // 單次觸發
// 請求 Qt 官網
QNetworkAccessManager manager;
QNetworkRequest request;
request.setUrl(QUrl("http://qt-project.org" ));
request.setRawHeader("User-Agent", "MyOwnBrowser 1.0");
QNetworkReply *pReply = manager.get(request);
QEventLoop loop;
connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
connect(pReply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
timer.start();
loop.exec(); // 啟動事件迴圈
if (timer.isActive()) { // 處理響應
timer.stop();
if (pReply->error() != QNetworkReply::NoError) {
// 錯誤處理
qDebug() << "Error String : " << pReply->errorString();
} else {
QVariant variant = pReply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
int nStatusCode = variant.toInt();
// 根據狀態碼做進一步資料處理
//QByteArray bytes = pReply->readAll();
qDebug() << "Status Code : " << nStatusCode;
}
} else { // 處理超時
disconnect(pReply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
pReply->abort();
pReply->deleteLater();
qDebug() << "Timeout";
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
首先,定義一個 QTimer,設定超時時間為 30000 毫秒(30 秒)並設定為單次觸發。然後,使用 QNetworkRequest 實現一個簡單的網路請求,通過 QNetworkAccessManager::get() 開始獲取 Qt 官網的 HTML 頁面內容。因為請求過程是非同步的,所以通過使用 QEventLoop 啟動一個事件迴圈讓其同步處理,並將 QTimer 的 timeout() 訊號以及 QNetworkReply 的 finished() 訊號連線至其 quit() 槽函式,保證在定時器過期之後或者網路響應完成後事件迴圈得到退出,不至於一直處於阻塞狀態。
如上所述,事件迴圈退出的兩種情況:
- QTimer 30 秒到期,超時
- 網路連線響應完成
所以,當 QTimer::isActive() 啟用的情況下,證明響應完成,還尚未超時。這時需要先呼叫 QTimer::stop() 來停止定時器,再對響做進一步處理。否則,進行超時處理 - QNetworkReply::abort() 立即中止操作並關閉網路連線。
封裝類
既然以後會經常用到,那麼還是提供一個封裝類 QReplyTimeout 專門處理超時。
#include <QObject>
#include <QTimer>
#include <QNetworkReply>
class QReplyTimeout : public QObject {
Q_OBJECT
public:
QReplyTimeout(QNetworkReply *reply, const int timeout) : QObject(reply) {
Q_ASSERT(reply);
if (reply && reply->isRunning()) { // 啟動單次定時器
QTimer::singleShot(timeout, this, SLOT(onTimeout()));
}
}
signals:
void timeout(); // 超時訊號 - 供進一步處理
private slots:
void onTimeout() { // 處理超時
QNetworkReply *reply = static_cast<QNetworkReply*>(parent());
if (reply->isRunning()) {
reply->abort();
reply->deleteLater();
emit timeout();
}
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
由於 QNetworkReply 和 QReplyTimeout 是父子關係,所以 QReplyTimeout 將被自動銷燬。
使用起來非常簡單:
QNetworkAccessManager *pManger = new QNetworkAccessManager(this);
QNetworkReply *pReply = pManger->get(QNetworkRequest(QUrl("https://www.google.com")));
QReplyTimeout *pTimeout = new QReplyTimeout(pReply, 1000);
// 超時進一步處理
connect(pTimeout, &QReplyTimeout::timeout, [=]() {
qDebug() << "Timeout";
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
如果對 Google 的獲取未在 1000 毫秒(1 秒)內完成,則會中止,併發出 timeout() 訊號,供進一步處理(例如:提示使用者請求超時)。