一、前言
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 請求的封裝等。
點選領取Qt學習資料+視訊教程~「連結」