• Qt 访问网络


    一、前言

    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 访问网络

    Qt实现同步(阻塞式)http get等网络访问操作


  • 相关阅读:
    QT两个字符串转化函数,避免文字乱码。
    QT隐藏工具栏上的右键菜单
    QT线程初次使用。遇到的问题。
    QTableView根据标题文字和表格文字自适应宽度
    void QTableView::setColumnWidth ( int column, int width),隐藏列不起作用
    在VS2010中去掉ipch和sdf文件方法
    QT的一个奇怪问题,设置了Qt::Tool后,点击弹出对话框的确定取消按钮,程序直接退出
    上传图片并显示缩略图的最简单方法(c#)
    总结一下散乱的开发点滴(2) (高手勿入)
    在global里捕获黄页并写入异常日志库
  • 原文地址:https://www.cnblogs.com/linuxAndMcu/p/14722265.html
Copyright © 2020-2023  润新知