1. 程式人生 > >Qt之處理QNetworkAccessManager網路連線超時

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() 訊號,供進一步處理(例如:提示使用者請求超時)。