【Qt筆記】訪問網絡(1)

如今的應用程序不多有純粹單機的。大部分爲了各類目的都須要聯網操做。爲此,Qt 提供了本身的網絡訪問庫,方便咱們對網絡資源進行訪問。本章咱們將介紹如何使用 Qt 進行最基本的網絡訪問。設計模式

Qt 進行網絡訪問的類是QNetworkAccessManager,這是一個名字至關長的類,不過使用起來並不像它的名字同樣複雜。爲了使用網絡相關的類,你須要在 pro 文件中添加QT += networkapi

 

QNetworkAccessManager類容許應用程序發送網絡請求以及接受服務器的響應。事實上,Qt 的整個訪問網絡 API 都是圍繞着這個類進行的。QNetworkAccessManager保存發送的請求的最基本的配置信息,包含了代理和緩存的設置。最好的是,這個 API 自己就是異步設計,這意味着咱們不須要本身爲其開啓線程,以防止界面被鎖死(這裏咱們能夠簡單瞭解下,Qt 的界面活動是在一個主線程中進行。網絡訪問是一個至關耗時的操做,若是整個網絡訪問的過程以同步的形式在主線程進行,則當網絡訪問沒有返回時,主線程會被阻塞,界面就會被鎖死,不能執行任何響應,甚至包括一個表明響應進度的滾動條都會被卡死在那裏。這種設計顯然是不友好的。)。異步的設計避免了這一系列的問題,可是卻要求咱們使用更多的代碼來監聽返回。這相似於咱們前面提到的QDialog::exec()QDialog::show()之間的區別。QNetworkAccessManager是使用信號槽來達到這一目的的。緩存

一個應用程序僅須要一個QNetworkAccessManager類的實例。因此,雖然QNetworkAccessManager自己沒有被設計爲單例,可是咱們應該把它當作單例使用。一旦一個QNetworkAccessManager實例建立完畢,咱們就可使用它發送網絡請求。這些請求都返回QNetworkReply對象做爲響應。這個對象通常會包含有服務器響應的數據。安全

下面咱們用一個例子來看如何使用QNetworkAccessManager進行網絡訪問。這個例子不只會介紹QNetworkAccessManager的使用,還將設計到一些關於程序設計的細節。服務器

咱們的程序是一個簡單的天氣預報的程序,使用 OpenWeatherMap 的 API 獲取數據。咱們能夠在這裏找到其 API 的具體介紹。網絡

咱們前面說過,通常一個應用使用一個QNetworkAccessManager就能夠知足須要,所以咱們本身封裝一個NetWorker類,並把這個類做爲單例。注意,咱們的代碼使用了 Qt5 進行編譯,所以若是你須要將代碼使用 Qt4 編譯,請自行修改相關部分。異步

// !!! Qt5
#ifndef NETWORKER_H
#define NETWORKER_H

#include <QObject>

class QNetworkReply;

class NetWorker : public QObject
{
    Q_OBJECT
public:
    static NetWorker * instance();
    ~NetWorker();

    void get(const QString &url);
signals:
    void finished(QNetworkReply *reply);
private:
    class Private;
    friend class Private;
    Private *d;

    explicit NetWorker(QObject *parent = 0);
    NetWorker(const NetWorker &) Q_DECL_EQ_DELETE;
    NetWorker& operator=(NetWorker rhs) Q_DECL_EQ_DELETE;
};

#endif // NETWORKER_H

NetWorker是一個單例類,所以它有一個instance()函數用來得到這惟一的實例。做爲單例模式,要求構造函數、拷貝構造函數和賦值運算符都是私有的,所以咱們將這三個函數都放在 private 塊中。注意咱們增長了一個Q_DECL_EQ_DELETE宏。這個宏是 Qt5 新增長的,意思是將它所修飾的函數聲明爲 deleted(這是 C++11 的新特性)。若是編譯器支持= delete語法,則這個宏將會展開爲= delete,不然則展開爲空。咱們的NetWorker只有一個get函數,顧名思義,這個函數會執行 HTTP GET 操做;一個信號finished(),會在獲取到服務器響應後發出。private 塊中還有三行關於Private的代碼:函數

class Private;
friend class Private;
Private *d;

這裏聲明瞭一個NetWorker的內部類,而後聲明瞭這個內部類的 d 指針。d 指針是 C++ 程序經常使用的一種設計模式。它的存在於 C++ 程序的編譯有關。在 C++ 中,保持二進制兼容性很是重要。若是你可以保持二進制兼容,則當之後升級庫代碼時,用戶不須要從新編譯本身的程序便可直接運行(若是你使用 Qt5.0 編譯了一個程序,這個程序不須要從新編譯就能夠運行在 Qt5.1 下,這就是二進制兼容;若是不須要修改源代碼,可是必須從新編譯才能運行,則是源代碼兼容;若是必須修改源代碼而且再通過編譯,例如從 Qt4 升級到 Qt5,則稱兩者是不兼容的)。保持二進制兼容的很重要的一個原則是不要隨意增長、刪除成員變量。由於這會致使類成員的尋址偏移量錯誤,從而破壞二進制兼容。爲了不這個問題,咱們將一個類的全部私有變量所有放進一個單獨的輔助類中,而在須要使用這些數據的類值提供一個這個輔助類的指針。注意,因爲咱們的輔助類是私有的,用戶不能使用它,因此針對這個輔助類的修改不會影響到外部類,從而保證了二進制兼容。關於二進制兼容的問題,咱們會在之後的文章中更詳細的說明,這裏僅做此簡單介紹。this

下面來看NetWorker的實現。編碼

class NetWorker::Private
{
public:
    Private(NetWorker *q) :
        manager(new QNetworkAccessManager(q))
    {}

    QNetworkAccessManager *manager;
};

PrivateNetWorker的內部類,扮演者前面咱們所說的那個輔助類的角色。NetWorker::Private類主要有一個成員變量QNetworkAccessManager *,把QNetworkAccessManager封裝起來。NetWorker::Private須要其被輔助的類NetWorker的指針,目的是做爲QNetworkAccessManager的 parent,以便NetWorker析構時可以自動將QNetworkAccessManager析構。固然,咱們也能夠經過將NetWorker::Private聲明爲QObject的子類來達到這一目的。

NetWorker *NetWorker::instance()
{
    static NetWorker netWorker;
    return &netWorker;
}

instance()函數很簡單,咱們聲明瞭一個 static 變量,將其指針返回。這是 C++ 單例模式的最簡單寫法,因爲 C++ 標準要求類的構造函數不能被打斷,所以這樣作也是線程安全的。

NetWorker::NetWorker(QObject *parent) :
    QObject(parent),
    d(new NetWorker::Private(this))
{
    connect(d->manager, &QNetworkAccessManager::finished,
            this, &NetWorker::finished);
}

NetWorker::~NetWorker()
{
    delete d;
    d = 0;
}

構造函數參數列表咱們將 d 指針進行賦值。構造函數內容很簡單,咱們將QNetworkAccessManagerfinished()信號進行轉發。也就是說,當QNetworkAccessManager發出finished()信號時,NetWorker一樣會發出本身的finished()信號。析構函數將 d 指針刪除。因爲NetWorker::Private是在堆上建立的,而且沒有繼承QObject,因此咱們必須手動調用delete運算符。

void NetWorker::get(const QString &url)
{
    d->manager->get(QNetworkRequest(QUrl(url)));
}

get()函數也很簡單,直接將用戶提供的 URL 字符串提供給底層的QNetworkAccessManager,其實是將操做委託給底層QNetworkAccessManager進行。

如今咱們將 QNetworkAccessManager進行了簡單的封裝。下一章咱們開始針對 OpenWeatherMap 的 API 進行編碼。

相關文章
相關標籤/搜索