如今的應用程序不多有純粹單機的。大部分爲了各類目的都須要聯網操做。爲此,Qt 提供了本身的網絡訪問庫,方便咱們對網絡資源進行訪問。本章咱們將介紹如何使用 Qt 進行最基本的網絡訪問。設計模式
Qt 進行網絡訪問的類是QNetworkAccessManager
,這是一個名字至關長的類,不過使用起來並不像它的名字同樣複雜。爲了使用網絡相關的類,你須要在 pro 文件中添加QT += network
。api
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; };
Private
是NetWorker
的內部類,扮演者前面咱們所說的那個輔助類的角色。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 指針進行賦值。構造函數內容很簡單,咱們將QNetworkAccessManager
的finished()
信號進行轉發。也就是說,當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 進行編碼。