如今的应用程序不多有纯粹单机的。大部分为了各类目的都须要联网操做。为此,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 进行编码。