ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Qt 之 QNetworkAccessManager踏坑记录

2021-06-13 11:33:39  阅读:408  来源: 互联网

标签:Qt pconf list 网络 4G reply 踏坑 QNetworkAccessManager


提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


项目背景

项目中,要用到QNetworkAccessManager 进行与云端的post、get、put请求,没考虑很多,就选择了Qt 的 QNetworkAccessManager模块,封装的很方便。但是因为是基于arm linux来开发的,切需要进行wifi和4G的网络切换,遇到了各种问题。


本项目踏坑是基于 QT 5.10.0版本,一个很冷门的版本。

一、常规用法 1

示例:

void ApiManager::sendApiLogin()
{
    QUrl url(m_strApiHead + "iot-server/api/pen/device/login");

    QList<QString> list;
    DevConfig *pconf = DevConfig::GetInstance();
    list.append("sn="+pconf->getSN());
    list.append("token="+pconf->getTokenZ03());
    list.append("local="+pconf->getLanguage());
    qint64 stamp = pconf->getCurStampMS();
    list.append(QString("stamp=%1").arg(stamp));
    list.append("bt_mac="+pconf->getBtMac());
    list.append("wifi_mac="+pconf->getWifiMac());
    list.append("version="+pconf->getVersion());
    list.sort();

    QString checksum = pconf->getApiCheckSum(list,pconf->getSecret());

    QUrlQuery postData;
    postData.addQueryItem("sn", pconf->getSN());
    postData.addQueryItem("token", pconf->getTokenZ03());
    postData.addQueryItem("local", pconf->getLanguage());
    postData.addQueryItem("stamp", QString("%1").arg(stamp));
    postData.addQueryItem("sig",checksum);
    postData.addQueryItem("wifi_mac", pconf->getWifiMac());
    postData.addQueryItem("bt_mac", pconf->getBtMac());
    postData.addQueryItem("version", pconf->getVersion());
    qDebug()<< "post = " << postData.toString(QUrl::FullyEncoded).toUtf8();

    QNetworkRequest request(url);
    request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true);
    request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");

    QNetworkReply *pNetworkResponse = m_pmanager->post(request,postData.toString(QUrl::FullyEncoded).toUtf8());
    connect(pNetworkResponse, SIGNAL(error(QNetworkReply::NetworkError)),
            this, SLOT(httpError(QNetworkReply::NetworkError)));
    QObject::connect(pNetworkResponse, &QNetworkReply::finished, [=]{
        QByteArray res =  pNetworkResponse->readAll();
        QJsonObject json_object = QJsonDocument::fromJson(res).object();
        int code = json_object.value("code").toInt();
        if (code != 200)
        {
            qDebug() << tr("登录认证失败");
            emit sig_recvErr(code);
            pNetworkResponse->close();
            pNetworkResponse->deleteLater();
            return;
        }
        QJsonValue data = json_object["data"];
        bool bbinded = data["binded"].toInt();
        DevConfig *pconf = DevConfig::GetInstance();
        pconf->setBinded(bbinded);
        bool bhalted = data["halted"].toInt();
        pconf->setHalted(bhalted);
        QString token = data["token"].toString();
        pconf->setTokenZ03(token);
        QString qr = data["qr"].toString();
        pconf->setQR(qr);
        pconf->GenerateQRcode(qr,118,118);
        pNetworkResponse->close();
        pNetworkResponse->deleteLater();

        emit sig_loginSuccess();
    });
}

正常情况下,通过QNetworkAccessManager 来进行post 、 get请求就如上例,用法很简单。

二、网络常规切换

QNetworkAccessManager 是QT的网络大管家,带有网络配置和管理功能,当正常的网络切换,包括4G网和wifi网的切换,包括网络是否可用(不是指网络不能通外网),都会有相应的信号发出,用起来非常方便

比如
网络是否在线通过 QNetworkConfigurationManager::onlineStateChanged 来判断
网络是否可用通过 QNetworkAccessManager::networkAccessibleChanged 来判断
网络的切换、配置变化通过QNetworkConfigurationManager::configurationChanged 信号发出

正常情况下,网络的管理是可以满足用户需求的。

#include <QNetworkAccessManager>
#include <QNetworkConfigurationManager>
int main(int argc, char *argv[])
{
    //QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);
    QTextCodec *codec = QTextCodec::codecForName("UTF-8");
    QTextCodec::setCodecForLocale(codec);


	QNetworkConfigurationManager manager;
    qDebug() << "main  defaultConfiguration" << manager.defaultConfiguration().name();

    QObject::connect(&manager, &QNetworkConfigurationManager::configurationAdded, [=](const QNetworkConfiguration &config){
        qDebug() << "configurationAdded" << config.name() << config.bearerTypeName();
    });

    QObject::connect(&manager, &QNetworkConfigurationManager::configurationRemoved, [=](const QNetworkConfiguration &config){
        qDebug() << "configurationRemoved" << config.name() << config.bearerTypeName();
    });

    //qt  network manager
    QNetworkAccessManager netManager;
    QNetworkConfiguration curConfig =  netManager.activeConfiguration();
    qDebug()<< "netManager.activeConfiguration() " << curConfig.name() << curConfig.bearerType() << curConfig.bearerTypeName();

    QNetworkConfiguration defaltConfig =  netManager.configuration();
    qDebug()<< "cnetManager.configuration()" << defaltConfig.name() << defaltConfig.bearerType() << defaltConfig.bearerTypeName();

    static int i = 0;
    qDebug() << "main " << netManager.activeConfiguration().name();
    qDebug() << "main " << netManager.configuration().name();
    networkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility accessible)
    QObject::connect(&netManager, &QNetworkAccessManager::networkAccessibleChanged, [=,&netManager,&manager]{
        //qDebug() << "QNetworkAccessManager networkAccessibleChanged";
        i++;
        qDebug()<< "--------- networkAccessibleChanged" << "num=" << i
                << netManager.configuration().name()
                << netManager.configuration().bearerType()
                << netManager.configuration().bearerTypeName();

        qDebug() << "--------- defaultConfiguration" << manager.defaultConfiguration().name();
    });


    QObject::connect(&manager, &QNetworkConfigurationManager::onlineStateChanged, [=](bool isonline){
        qDebug() << " ######## onlineStateChanged" << isonline;
    });

    QObject::connect(&manager, &QNetworkConfigurationManager::configurationChanged, [=,&netManager,&manager](const QNetworkConfiguration &config){
        qDebug() << "********** configurationChanged" << config.name() << config.bearerType()<< config.bearerTypeName();

   });

    QObject::connect(&manager, &QNetworkConfigurationManager::updateCompleted, [=]{
        qDebug() << "updateCompleted" ;
    });

    return app.exec();
}

二、踏坑bug

当wifi和4G进行切换时

1 当4G网络和WiFi网络进行切换时,报错 UnknownNetworkError

网上给出的解决方法是:
只需要在QNetworkAccessManager执行get或者post的时候,获取一下NetworkAccessible的状态, 再设置一下就好了,如果是QNetworkAccessManager::NotAccessible 状态,设置其可用即可:

//判断一下网络状态, 如果为NotAccessible 重新设置一下
if(m_NetManager->networkAccessible() == QNetworkAccessManager::NotAccessible){
    m_NetManager->setNetworkAccessible(QNetworkAccessManager::Accessible);
}
QNetworkReply *reply = m_NetManager->post(request, data);

但是给本项目又带来了不可预料的结果。如下面的第二个bug

2.设置setNetworkAccessible(QNetworkAccessManager::Accessible),会把4G打开

当网络不可用时,本人尝试了设置

if(m_NetManager->networkAccessible() == QNetworkAccessManager::NotAccessible){
    m_NetManager->setNetworkAccessible(QNetworkAccessManager::Accessible);
}

系统开发人员也是摸索了发现,每次网络切换时,QT程序会把4G打开,找了好久才发现是上句代码给打开的。坑啊。也不清楚qt通过什么方式来检测到,会把4G给打开。

3. 当已经连接到wifi网络时,启动qt程序直接崩溃

WiFi 网络连接管理时通过 wpa来管理,当硬件已经连接上了wifi,再启动QT程序,竟然大概率的直接崩溃。
后来通过日志发现:每次启动QT程序,

QNetworkConfigurationManager manager;
    qDebug() << "main  defaultConfiguration" << manager.defaultConfiguration().name();

上述打印的网络配置均为中国电信,即是4G网的配置,感觉是这块引起的异常,因为QNetworkAccessManager 本身有网络管理能力,当当前链路为wifi时,正确的配置应该时wlan配置,测试了好久,断定应该是在该arm系统下,系统的适配有问题,QNetworkAccessManager 的网络管理适配错误。
那如何解决呢?
既然因为QT的网络管理出错,能不能只让QNetworkAccessManager提供基本的网络post、get请求能力,而不让QNetworkAccessManager进行网络管理呢?看QNetworkAccessManager类发现有下面宏定义:

#ifndef QT_NO_BEARERMANAGEMENT
    void setConfiguration(const QNetworkConfiguration &config);
    QNetworkConfiguration configuration() const;
    QNetworkConfiguration activeConfiguration() const;

    void setNetworkAccessible(NetworkAccessibility accessible);
    NetworkAccessibility networkAccessible() const;
#endif

可以发现网络的配置管理基本都在该***QT_NO_BEARERMANAGEMENT***定义之内 。所以尝试把该宏定义关闭,重新交叉编译QT库,发现问题解决。

4. 使用了QNetworkAccessManager的get,post网络请求接口报错:QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once

网上给的解决方式:
当进行poset请求过程时,如果此时的m_reply已经处于错误状态,则不要调用 m_reply->abort();
或者 m_reply->close();

  QNetworkAccessManager *manager = new QNetworkAccessManager(this);
  manager->get(QNetworkRequest(QUrl("http://qt-project.org")));
  m_reply = m_networkManager.get(QNetworkRequest(requestedUrl));
connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), 
		this, SLOT(httpError(QNetworkReply::NetworkError)));

可以获得几乎所有Qt自定义的http错误(也可以自己增加新的错误码)
这样做也没用:

 if (m_reply->isRunning())
{
	m_reply->abort();  //也不要m_reply->close(); 
}

http处于错误时,m_reply->isRunning()还返回true,Qt文档写得不太清楚。此时再调用m_reply->abort(); 或者m_reply->close();
都会报错:

QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once.

此时应该

m_reply->deleteLater();
m_reply = nullptr;

总结

qt的网络管理很方便,但是真正遇到复杂的情况,自己还是要清楚哪里出了问题,做些深入的研究啊

标签:Qt,pconf,list,网络,4G,reply,踏坑,QNetworkAccessManager
来源: https://blog.csdn.net/u011942101/article/details/117870397

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有