Qt 中访问网络使用 QNetworkAccessManager
,它的 API 是异步,这样在访问网络的时候不需要启动一个线程,在线程里执行请求的代码。
需要注意一点的是,请求响应的对象 QNetworkReply
需要我们自己手动的删除,一般都会在 QNetworkAccessManager::finished
信号的曹函数里使用 reply->deleteLater()
删除,不要直接 delete reply
。
本文的最终结果为实现调用一个函数就能访问网络:
1 2 3 4 5 QNetworkAccessManager *manager = new QNetworkAccessManager (); NetworkUtil::get (manager, "http://www.baidu.com" , [](const QString &response) { qDebug () << response; });
例一 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #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 (); }); QObject::connect (reply, &QNetworkReply::finished, [&] { reply->deleteLater (); }); return app.exec (); }
例二 仔细观察上面程序的输出结果,由于返回的数据比较大,readyRead 被调用了多次
,而不是一次性就得到了请求的响应数据,这个特点在某些情况下很有用,例如下载 100M 的文件,多次读取肯定是合适的,因为读取后数据就会从 reply 中删除,不会导致占用太多内存,但是在某些情况下却不太好用,例如读取一个响应 JSON 的数据,一般都不会太大,大的也就几十上百 K,如果一次得不到 JSON 的全部数据,多次读取的情况下想要拼出一个完整的 JSON 字符串不太容易,这时如果能一次性的得到响应的 JSON 数据是不是就很方便了呢?
要一次性读取到响应的数据可以在 QNetworkReply::finished
信号处理中进行,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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 (); }
优化 观察上面的程序,会发现很多代码都是重复的模版代码,例如
创建 QNetworkRequest
获取 QNetworkReply
删除 QNetworkReply
错误处理
响应处理
大量的模版代码可以把它们封装成一个工具类,方便使用,参考下面 main()
函数里的调用,代码一下子看上去就清晰简单了很多。
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include "NetworkUtil.h" #include <QDebug> #include <QApplication> #include <QNetworkAccessManager> int main (int argc, char *argv[]) { QApplication app (argc, argv) ; QNetworkAccessManager *manager = new QNetworkAccessManager (); NetworkUtil::get (manager, "http://www.baidu.com" , [](const QString &response) { qDebug () << response; }); NetworkUtil::get (manager, "http://www.163.com" , [](const QString &response) { qDebug () << response; }, NULL , "GB2312" ); return app.exec (); }
NetworkUtil.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #ifndef NETWORKUTIL_H #define NETWORKUTIL_H #include <functional> class QString ;class QNetworkAccessManager ;class NetworkUtil {public : static void get (QNetworkAccessManager *manager, const QString &url, std::function<void (const QString &)> successHandler, std::function<void (const QString &)> errorHandler = NULL , const char *encoding = "UTF-8" ) ;}; #endif
NetworkUtil.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include "NetworkUtil.h" #include <QNetworkRequest> #include <QNetworkReply> #include <QNetworkAccessManager> #include <QTextStream> void NetworkUtil::get (QNetworkAccessManager *manager, const QString &url, std::function<void (const QString &)> successHandler, std::function<void (const QString &)> errorHandler, 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 (); }); }
思考 工具类 NetworkUtil
介绍了一次性读取时 Get 的封装,大家思考一下 Get 多次读取的封装,Post 请求的封装等。
挑战 线程 MyThread 每隔 2 秒发出信号通知 MyWidget 访问 http://www.baidu.com ,然后把响应的数据显示到 MyWidget 上的 QTextEdit 中。
不是说 QNetworkAccessManager
的 API 是异步的么,为啥这里又需要用线程了?曾经遇到这么一个需求,连接身份证读卡器,不停自动的读取身份证信息,上传到 Web 服务器,也就是这个挑战能使用到的场景,你可以把它换成不停的读取 NFC 卡,刷门禁卡等。因为是不停的读卡,所以需要在线程中读卡,否则界面会被冻结掉。