Qt 之 QNetworkAccessManager踏坑记录

2024-06-20 07:48

本文主要是介绍Qt 之 QNetworkAccessManager踏坑记录,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

文章目录

  • 项目背景
  • 一、常规用法 1
  • 二、网络常规切换
  • 二、踏坑bug
    • 当wifi和4G进行切换时
      • 1 当4G网络和WiFi网络进行切换时,报错 UnknownNetworkError
    • 2.设置setNetworkAccessible(QNetworkAccessManager::Accessible),会把4G打开
    • 3. 当已经连接到wifi网络时,启动qt程序直接崩溃
    • 4. 使用了QNetworkAccessManager的get,post网络请求接口报错:QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once
  • 总结
  • 更新


项目背景

项目中,要用到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 managerQNetworkAccessManager 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_BEARERMANAGEMENTvoid 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的网络管理很方便,但是真正遇到复杂的情况,自己还是要清楚哪里出了问题,做些深入的研究啊

更新

好消息,随着代码研究的深入,发现QNetworkAccessManager的clearAccessCache(),有可能解决网络切换导致崩溃的问题。目前的做法是当网络切换时,比如 wifi->4G 4G->wifi 切换时,调用下clearAccessCache函数。意外惊喜啊。

void QNetworkAccessManager::clearAccessCache()
Flushes the internal cache of authentication data and network connections.
This function is useful for doing auto tests.
This function was introduced in Qt 5.0.
See also clearConnectionCache().

目前最新代码:

void GlobalNetworkManager::resetNetworkManager()
{QMutexLocker locker(&netMangeerMutex);if(globalManager){QList<QNetworkReply *> list =  globalManager->findChildren<QNetworkReply *>();qDebug() << __func__ << "child size=" << list.size();foreach (QNetworkReply *t, list) {qDebug() << __func__ << "before abort";t->abort();t->deleteLater();}}qDebug() << __func__;globalManager->clearAccessCache();
}

这篇关于Qt 之 QNetworkAccessManager踏坑记录的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1077527

相关文章

Qt实现发送HTTP请求的示例详解

《Qt实现发送HTTP请求的示例详解》这篇文章主要为大家详细介绍了如何通过Qt实现发送HTTP请求,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1、添加network模块2、包含改头文件3、创建网络访问管理器4、创建接口5、创建网络请求对象6、创建一个回复对

Spring Retry 实现乐观锁重试实践记录

《SpringRetry实现乐观锁重试实践记录》本文介绍了在秒杀商品SKU表中使用乐观锁和MybatisPlus配置乐观锁的方法,并分析了测试环境和生产环境的隔离级别对乐观锁的影响,通过简单验证,... 目录一、场景分析 二、简单验证 2.1、可重复读 2.2、读已提交 三、最佳实践 3.1、配置重试模板

在 Spring Boot 中使用异步线程时的 HttpServletRequest 复用问题记录

《在SpringBoot中使用异步线程时的HttpServletRequest复用问题记录》文章讨论了在SpringBoot中使用异步线程时,由于HttpServletRequest复用导致... 目录一、问题描述:异步线程操作导致请求复用时 Cookie 解析失败1. 场景背景2. 问题根源二、问题详细分

Qt 中集成mqtt协议的使用方法

《Qt中集成mqtt协议的使用方法》文章介绍了如何在工程中引入qmqtt库,并通过声明一个单例类来暴露订阅到的主题数据,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录一,引入qmqtt 库二,使用一,引入qmqtt 库我是将整个头文件/源文件都添加到了工程中进行编译,这样 跨平台

关于Spring @Bean 相同加载顺序不同结果不同的问题记录

《关于Spring@Bean相同加载顺序不同结果不同的问题记录》本文主要探讨了在Spring5.1.3.RELEASE版本下,当有两个全注解类定义相同类型的Bean时,由于加载顺序不同,最终生成的... 目录问题说明测试输出1测试输出2@Bean注解的BeanDefiChina编程nition加入时机总结问题说明

将sqlserver数据迁移到mysql的详细步骤记录

《将sqlserver数据迁移到mysql的详细步骤记录》:本文主要介绍将SQLServer数据迁移到MySQL的步骤,包括导出数据、转换数据格式和导入数据,通过示例和工具说明,帮助大家顺利完成... 目录前言一、导出SQL Server 数据二、转换数据格式为mysql兼容格式三、导入数据到MySQL数据

关于rpc长连接与短连接的思考记录

《关于rpc长连接与短连接的思考记录》文章总结了RPC项目中长连接和短连接的处理方式,包括RPC和HTTP的长连接与短连接的区别、TCP的保活机制、客户端与服务器的连接模式及其利弊分析,文章强调了在实... 目录rpc项目中的长连接与短连接的思考什么是rpc项目中的长连接和短连接与tcp和http的长连接短

Oracle查询优化之高效实现仅查询前10条记录的方法与实践

《Oracle查询优化之高效实现仅查询前10条记录的方法与实践》:本文主要介绍Oracle查询优化之高效实现仅查询前10条记录的相关资料,包括使用ROWNUM、ROW_NUMBER()函数、FET... 目录1. 使用 ROWNUM 查询2. 使用 ROW_NUMBER() 函数3. 使用 FETCH FI

Python MySQL如何通过Binlog获取变更记录恢复数据

《PythonMySQL如何通过Binlog获取变更记录恢复数据》本文介绍了如何使用Python和pymysqlreplication库通过MySQL的二进制日志(Binlog)获取数据库的变更记录... 目录python mysql通过Binlog获取变更记录恢复数据1.安装pymysqlreplicat

基于Qt Qml实现时间轴组件

《基于QtQml实现时间轴组件》时间轴组件是现代用户界面中常见的元素,用于按时间顺序展示事件,本文主要为大家详细介绍了如何使用Qml实现一个简单的时间轴组件,需要的可以参考下... 目录写在前面效果图组件概述实现细节1. 组件结构2. 属性定义3. 数据模型4. 事件项的添加和排序5. 事件项的渲染如何使用