一、前言
Qt 中访问网络使用 QNetworkAccessManager
,它的 API 是异步的,这样在访问网络的时候不需要启动一个线程,在线程里执行请求的代码。(但这一点在有时候需要阻塞时就是个麻烦了)
需要注意一点的是,请求响应的对象 QNetworkReply
需要我们自己手动的删除,一般都会在 QNetworkAccessManager::finished
信号的曹函数里使用 reply->deleteLater()
删除,不要直接 delete reply
。
二、示例一
#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();
});
// 请求结束时删除 reply 释放内存
QObject::connect(reply, &QNetworkReply::finished, [&] {
reply->deleteLater();
});
return app.exec();
}
仔细观察上面程序的输出结果,由于返回的数据比较大,readyRead 被调用了多次
,而不是一次性就得到了请求的响应数据,这个特点在某些情况下很有用,例如下载 100M 的文件,多次读取肯定是合适的,因为读取后数据就会从 reply 中删除,不会导致占用太多内存。
但是在某些情况下却不太好用,例如读取一个响应 JSON 的数据,一般都不会太大,大的也就几十上百 K,如果一次得不到 JSON 的全部数据,多次读取的情况下想要拼出一个完整的 JSON 字符串不太容易,这时如果能一次性的得到响应的 JSON 数据是不是就很方便了呢?
三、示例二
要一次性读取到响应的数据可以在 QNetworkReply::finished
信号处理中进行,如下:
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();
}
四、封装HTTP网络工具类
观察上面的程序,会发现很多代码都是重复的模版代码,例如
- 创建 QNetworkRequest
- 获取 QNetworkReply
- 删除 QNetworkReply
- 错误处理
- 响应处理
大量的模版代码可以把它们封装成一个工具类,方便使用,参考下面 main()
函数里的调用,代码一下子看上去就清晰简单了很多。
main.cpp
#include "NetworkUtil.h"
#include <QDebug>
#include <QApplication>
#include <QNetworkAccessManager>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QNetworkAccessManager *manager = new QNetworkAccessManager();
// 访问 baidu
NetworkUtil::get(manager, "http://www.baidu.com", [](const QString &response) {
qDebug() << response;
}); // 默认的异步方式
// 访问 163
NetworkUtil::get(manager, "http://www.163.com", [](const QString &response) {
qDebug() << response;
}, NULL, true, "GB2312"); // 同步方式
return app.exec();
}
NetworkUtil.h
#ifndef NETWORKUTIL_H
#define NETWORKUTIL_H
#include <functional>
class QString;
class QNetworkAccessManager;
class NetworkUtil {
public:
/**
* @brief 使用 GET 访问网络
*
* @param manager QNetworkAccessManager 对象
* @param url 需要访问的 URL
* @param successHandler 访问成功的 Lambda 回调函数
* @param errorHandler 访问失败的 Lambda 回调函数
* @param async 默认为异步方式,false 以设置为同步(阻塞式)方式
* @param encoding 读取响应数据的编码
*/
static void get(QNetworkAccessManager *manager,
const QString &url,
std::function<void (const QString &)> successHandler,
std::function<void (const QString &)> errorHandler = NULL,
const bool &async = true,
const char *encoding = "UTF-8");
};
#endif // NETWORKUTIL_H
NetworkUtil.cpp
#include "NetworkUtil.h"
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QTextStream>
#include <QEventLoop>
void NetworkUtil::get(QNetworkAccessManager *manager,
const QString &url,
std::function<void (const QString &)> successHandler,
std::function<void (const QString &)> errorHandler,
const bool &async,
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();
});
// 异步,还是同步阻塞
if(!async) {
// 使用QEventLoop阻塞
QEventLoop eventLoop;
QObject::connect(reply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit);
eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
}
}
Qt 的网络操作类是异步(非阻塞的),所以这里使用 QEventLoop 来手动阻塞以实现同步。
这里暂时只介绍了一次性读取时 Get 的封装,后面考虑实现 Get 多次读取的封装,Post 请求的封装等。
参考: