天天看點

Qt 通路網絡

作者:QT教程

一、前言

Qt 中通路網絡使用 QNetworkAccessManager,它的 API 是異步的,這樣在通路網絡的時候不需要啟動一個線程,線上程裡執行請求的代碼。(但這一點在有時候需要阻塞時就是個麻煩了)

需要注意一點的是,請求響應的對象 QNetworkReply 需要我們自己手動的删除,一般都會在 QNetworkAccessManager::finished 信号的曹函數裡使用 reply->deleteLater() 删除,不要直接 delete reply。

二、示例一

#include <QDebug>
#include <QApplication>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkAccessManager>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    QNetworkAccessManager *manager = new QNetworkAccessManager();
    QNetworkRequest request(QUrl("http://www.baidu.com"));
    QNetworkReply *reply = manager->get(request);

    int count = 0;

    QObject::connect(reply, &QNetworkReply::readyRead, [&] {
        qDebug() << QString(reply->readAll());
        qDebug() << ++count;
    });
    
    // 請求錯誤處理
    QObject::connect(reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), [&] {
        qDebug() << reply->errorString();
    });

    // 請求結束時删除 reply 釋放記憶體
    QObject::connect(reply, &QNetworkReply::finished, [&] {
        reply->deleteLater();
    });

    return app.exec();
}
           

仔細觀察上面程式的輸出結果,由于傳回的資料比較大,readyRead 被調用了多次,而不是一次性就得到了請求的響應資料,這個特點在某些情況下很有用,例如下載下傳 100M 的檔案,多次讀取肯定是合适的,因為讀取後資料就會從 reply 中删除,不會導緻占用太多記憶體。

但是在某些情況下卻不太好用,例如讀取一個響應 JSON 的資料,一般都不會太大,大的也就幾十上百 K,如果一次得不到 JSON 的全部資料,多次讀取的情況下想要拼出一個完整的 JSON 字元串不太容易,這時如果能一次性的得到響應的 JSON 資料是不是就很友善了呢?

三、示例二

要一次性讀取到響應的資料可以在 QNetworkReply::finished 信号進行中進行,如下:

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    QNetworkAccessManager *manager = new QNetworkAccessManager();
    QNetworkRequest request(QUrl("http://www.baidu.com"));
    QNetworkReply *reply = manager->get(request);

    // 請求錯誤處理
    QObject::connect(reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), [&] {
        qDebug() << reply->errorString();
    });
    
    // 請求結束時一次性讀取所有響應資料
    QObject::connect(reply, &QNetworkReply::finished, [&] {
        if (reply->error() == QNetworkReply::NoError) {
            qDebug() << reply->readAll();
        }

        reply->deleteLater();
    });

    return app.exec();
}
           

四、封裝HTTP網絡工具類

觀察上面的程式,會發現很多代碼都是重複的模版代碼,例如

  • 建立 QNetworkRequest
  • 擷取 QNetworkReply
  • 删除 QNetworkReply
  • 錯誤處理
  • 響應處理

大量的模版代碼可以把它們封裝成一個工具類,友善使用,參考下面 main() 函數裡的調用,代碼一下子看上去就清晰簡單了很多。

main.cpp

#include "NetworkUtil.h"

#include <QDebug>
#include <QApplication>
#include <QNetworkAccessManager>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    QNetworkAccessManager *manager = new QNetworkAccessManager();

    // 通路 baidu
    NetworkUtil::get(manager, "http://www.baidu.com", [](const QString &response) {
        qDebug() << response;
    });  // 預設的異步方式

    // 通路 163
    NetworkUtil::get(manager, "http://www.163.com", [](const QString &response) {
        qDebug() << response;
    }, NULL, true, "GB2312"); // 同步方式

    return app.exec();
}
           

NetworkUtil.h

#ifndef NETWORKUTIL_H
#define NETWORKUTIL_H

#include <functional>

class QString;
class QNetworkAccessManager;

class NetworkUtil {
public:
    /**
     * @brief 使用 GET 通路網絡
     *
     * @param manager QNetworkAccessManager 對象
     * @param url 需要通路的 URL
     * @param successHandler 通路成功的 Lambda 回調函數
     * @param errorHandler 通路失敗的 Lambda 回調函數
     * @param async 預設為異步方式,false 以設定為同步(阻塞式)方式
     * @param encoding 讀取響應資料的編碼
     */
    static void get(QNetworkAccessManager *manager,
                    const QString &url,
                    std::function<void (const QString &)> successHandler,
                    std::function<void (const QString &)> errorHandler = NULL,
                    const bool &async = true,
                    const char *encoding = "UTF-8");
};

#endif // NETWORKUTIL_H
           

NetworkUtil.cpp

#include "NetworkUtil.h"
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QTextStream>
#include <QEventLoop>

void NetworkUtil::get(QNetworkAccessManager *manager,
                      const QString &url,
                      std::function<void (const QString &)> successHandler,
                      std::function<void (const QString &)> errorHandler,
                      const bool &async,
                      const char *encoding) {
    QUrl urlx(url);
    QNetworkRequest request(urlx);
    QNetworkReply *reply = manager->get(request);

    // 請求錯誤處理
    QObject::connect(reply, static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), [=] {
        if (NULL != errorHandler) {
            errorHandler(reply->errorString());
        }
    });

    // 請求結束時一次性讀取所有響應資料
    QObject::connect(reply, &QNetworkReply::finished, [=] {
        if (reply->error() == QNetworkReply::NoError) {
            // 讀取響應資料
            QTextStream in(reply);
            QString result;

            in.setCodec(encoding);
            while (!in.atEnd()) {
                result += in.readLine();
            }

            successHandler(result);
        }

        reply->deleteLater();
    });

    // 異步,還是同步阻塞
    if(!async) {
        // 使用QEventLoop阻塞
        QEventLoop eventLoop;
        QObject::connect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
        eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
    }
}
           

Qt 的網絡操作類是異步(非阻塞的),是以這裡使用 QEventLoop 來手動阻塞以實作同步。

這裡暫時隻介紹了一次性讀取時 Get 的封裝,後面考慮實作 Get 多次讀取的封裝,Post 請求的封裝等。

點選領取Qt學習資料+視訊教程~「連結」

繼續閱讀