#(65):訪問網絡(1)
現在的應用程序很少有純粹單機的。大部分為了各種目的都需要聯網操作。為此,Qt 提供了自己的網絡訪問庫,方便我們對網絡資源進行訪問。本章我們將介紹如何使用 Qt 進行最基本的網絡訪問。
Qt 進行網絡訪問的類是`QNetworkAccessManager`,這是一個名字相當長的類,不過使用起來并不像它的名字一樣復雜。為了使用網絡相關的類,你需要在 pro 文件中添加`QT += network`。
`QNetworkAccessManager`類允許應用程序發送網絡請求以及接受服務器的響應。事實上,Qt 的整個訪問網絡 API 都是圍繞著這個類進行的。`QNetworkAccessManager`保存發送的請求的最基本的配置信息,包含了代理和緩存的設置。最好的是,這個 API 本身就是異步設計,這意味著我們不需要自己為其開啟線程,以防止界面被鎖死(這里我們可以簡單了解下,Qt 的界面活動是在一個主線程中進行。網絡訪問是一個相當耗時的操作,如果整個網絡訪問的過程以同步的形式在主線程進行,則當網絡訪問沒有返回時,主線程會被阻塞,界面就會被鎖死,不能執行任何響應,甚至包括一個代表響應進度的滾動條都會被卡死在那里。這種設計顯然是不友好的。)。異步的設計避免了這一系列的問題,但是卻要求我們使用更多的代碼來監聽返回。這類似于我們前面提到的`QDialog::exec()`和`QDialog::show()`之間的區別。`QNetworkAccessManager`是使用信號槽來達到這一目的的。
一個應用程序僅需要一個`QNetworkAccessManager`類的實例。所以,雖然`QNetworkAccessManager`本身沒有被設計為單例,但是我們應該把它當做單例使用。一旦一個`QNetworkAccessManager`實例創建完畢,我們就可以使用它發送網絡請求。這些請求都返回`QNetworkReply`對象作為響應。這個對象一般會包含有服務器響應的數據。
下面我們用一個例子來看如何使用`QNetworkAccessManager`進行網絡訪問。這個例子不僅會介紹`QNetworkAccessManager`的使用,還將設計到一些關于程序設計的細節。
我們的程序是一個簡單的天氣預報的程序,使用 OpenWeatherMap 的 API 獲取數據。我們可以在[這里](http://api.openweathermap.org/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,則稱二者是不兼容的)。保持二進制兼容的很重要的一個原則是不要隨意增加、刪除成員變量。因為這會導致類成員的尋址偏移量錯誤,從而破壞二進制兼容。為了避免這個問題,我們將一個類的所有私有變量全部放進一個單獨的輔助類中,而在需要使用這些數據的類值提供一個這個輔助類的指針。注意,由于我們的輔助類是私有的,用戶不能使用它,所以針對這個輔助類的修改不會影響到外部類,從而保證了二進制兼容。關于二進制兼容的問題,我們會在以后的文章中更詳細的說明,這里僅作此簡單介紹。
下面來看`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 進行編碼。
- (1)序
- (2)Qt 簡介
- (3)Hello, world!
- (4)信號槽
- (5)自定義信號槽
- (6)Qt 模塊簡介
- (7)MainWindow 簡介
- (8)添加動作
- (9)資源文件
- (10)對象模型
- (11)布局管理器
- (12)菜單欄、工具欄和狀態欄
- (13)對話框簡介
- (14)對話框數據傳遞
- (15)標準對話框 QMessageBox
- (16)深入 Qt5 信號槽新語法
- (17)文件對話框
- (18)事件
- (19)事件的接受與忽略
- (21)事件過濾器
- (22)事件總結
- (23)自定義事件
- (24)Qt 繪制系統簡介
- (25)畫刷和畫筆
- (26)反走樣
- (27)漸變
- (28)坐標系統
- (29)繪制設備
- (30)Graphics View Framework
- (31)貪吃蛇游戲(1)
- (32)貪吃蛇游戲(2)
- (33)貪吃蛇游戲(3)
- (34)貪吃蛇游戲(4)
- (35)文件
- (36)二進制文件讀寫
- (37)文本文件讀寫
- (38)存儲容器
- (39)遍歷容器
- (40)隱式數據共享
- (41)model/view 架構
- (42)QListWidget、QTreeWidget 和 QTableWidget
- (43)QStringListModel
- (44)QFileSystemModel
- (45)模型
- (46)視圖和委托
- (47)視圖選擇
- (48)QSortFilterProxyModel
- (49)自定義只讀模型
- (50)自定義可編輯模型
- (51)布爾表達式樹模型
- (52)使用拖放
- (53)自定義拖放數據
- (54)剪貼板
- (55)數據庫操作
- (56)使用模型操作數據庫
- (57)可視化顯示數據庫數據
- (58)編輯數據庫外鍵
- (59)使用流處理 XML
- (60)使用 DOM 處理 XML
- (61)使用 SAX 處理 XML
- (62)保存 XML
- (63)使用 QJson 處理 JSON
- (64)使用 QJsonDocument 處理 JSON
- (65)訪問網絡(1)
- (66)訪問網絡(2)
- (67)訪問網絡(3)
- (68)訪問網絡(4)
- (69)進程
- (70)進程間通信
- (71)線程簡介
- (72)線程和事件循環
- (73)Qt 線程相關類
- (74)線程和 QObject
- (75)線程總結
- (76)QML 和 QtQuick 2
- (77)QML 語法
- (78)QML 基本元素
- (79)QML 組件
- (80)定位器
- (81)元素布局
- (82)輸入元素
- (83)Qt Quick Controls
- (84)Repeater
- (85)動態視圖
- (86)視圖代理
- (87)模型-視圖高級技術
- (88)Canvas
- (89)Canvas(續)