前面几章我们了解了如何使用QNetworkAccessManager
访问网络。在此基础上,我们已经实现了一个简单的查看天气的程序。在这个程序中,我们使用QNetworkAccessManager
进行网络的访问,从一个网络 API 获取某个城市的当前天气状况。
如果你仔细观察就会发现,即便我们没有添加任何相关代码,QNetworkAccessManager
的网络访问并不会阻塞 GUI 界面。也就是说,即便是在进行网络访问的时候,我们的界面还是可以响应的。相比之下,如果你对 Java 熟悉,就会了解到,在 Java 中,进行 Socket 通讯时,界面默认是阻塞的,当程序进行网络访问操作时,界面不能对我们的操作做出任何响应。由此可以看出,QNetworkAccessManager
的网络访问默认就是异步的、非阻塞的。这样的实现固然很好,也符合大多数程序的应用情形:我们当然希望程序界面能够始终对用户操作做出响应。不过,在某些情况下,我们还是希望会有一些同步的网络操作。典型的是登录操作。在登录时,我们必须要等待网络返回结果,才能让界面做出响应:是验证成功进入系统,还是验证失败做出提示?这就是本章的主要内容:如何使用QNetworkAccessManager
进行同步网络访问。
当我们重新运行先前编译好的程序,可以看看这样一个操作:由于我们的界面是不阻塞的,那么当我们第一次点击了 Refresh 按钮之后,马上切换城市再点击一次 Refresh 按钮,就会看到第一次的返回结果一闪而过。这是因为第一次网络请求尚未完成时,用户又发送了一次请求,Qt 会将两次请求的返回结果顺序显示。这样处理结果可能会出现与预期不一致的情况(比如第一次请求响应由于某种原因异常缓慢,第二次却很快,此时第二次结果会比第一次先到,那么很明显,当第一次结果返回时,第二次的结果就会被覆盖掉。我们假设认为用户需要第二次的返回,那么就会出现异常)。
要解决这种情况,我们可以在有网络请求时将界面锁死,不允许用户进行更多的操作(更好的方法是仅仅锁住某些按钮,而不是整个界面。不过这里我们以锁住整个界面为例)。我们的解决方案很简单:当QNetworkAccessManager
发出请求之后,我们进入一个新的事件循环,将操作进行阻塞。我们的代码示例如下:
void fetchWeather(const QString &cityName) { QEventLoop eventLoop; connect(netWorker, &NetWorker::finished, &eventLoop, &QEventLoop::quit); QNetworkReply *reply = netWorker->get(QString("http://api.openweathermap.org/data/2.5/weather?q=%1&mode=json&units=metric&lang=zh_cn").arg(cityName)); replyMap.insert(reply, FetchWeatherInfo); eventLoop.exec(); }
注意,我们在函数中创建了一个QEventLoop
实例,将其quit()
与NetWorker::finished()
信号连接起来。当NetWorker::finished()
信号发出时,QEventLoop::quit()
就会被调用。在NetWorker::get()
执行之后,调用QEventLoop::exec()
函数开始事件循环。此时界面就是被阻塞。
现在我们只是提供了一种很简单的思路。当然这并不是最好的思路:程序界面直接被阻塞,用户获得不了任何提示,会误以为程序死掉。更好的做法是做一个恰当的提示,不过这已经超出我们本章的范畴。更重要的是,这种思路并不完美。如果你的程序是控制台程序(没有 GUI 界面),或者是某些特殊的情况下,会造出死锁!控制台程序中发送死锁的原因在于在非 GUI 程序中另外启动事件循环会将主线程阻塞,QNetworkAccessManager
的所有信号都不会收到。“某些特殊的情况”,我们会在后面有关线程的章节详细解释。不过,要完美解决这个问题,我们必须使用另外的线程。这里有一个通用的解决方案,感兴趣的童鞋可以详细了解下。
19 评论
请问如何禁用窗口的拉伸光标?
你可以试试
QWidget
的setFixedSize()
函数窗口大小不变了,但是在窗体边缘的拉伸光标依旧在,这个我找文档的事件也没看到 lol 求指导, 我是用layout()->setSizeConstraint(QLayout::SetFixedSize);来禁止大小的,请问两者有什么差异吗?
如果你需要让窗口大小根据其内容固定,可以使用 setSizeConstraint() 函数;setFixedSize() 函数则是显式给定一个固定值。至于光标的问题,Qt4 是没有这个问题的;Qt5 的确存在这个问题,并且现在确定是一个 bug,在 Qt5.1.2 中将修复这个问题。
豆子 你好 我想在Qt下实现TCP网络通信,建立一个可以接受大量客户端的服务器程序,这个如何实现呢?
可以参考下QTcpServer和QTcpSocket这两个类
那Qt有没有那种像windows下完成端口的高性能处理多客户的线程池之类的模型呢?
这个不大清楚,不过你可以找个第三方 C++ 库都是可以的
谢谢你。博文不错,持续关注,继续加油
请问如何实现网络UDP(包括广播/组播和单播)和TCP通信,在工程中这2类方式用的很多,谢谢豆子
Qt 提供了 QUdpSocket 和 QTcpSocket 两个类用于实现 UDP 和 TCP。你可以看看这两个类的文档。之前因为感觉 UDP 和 TCP 相对大部分应用比较底层,所以没有详细说明
您说的控制台程序使用eventLoop.exec();会导致死锁,是因为在控制台程序里QNetworkAccessManager的操作或finish信号不是在eventLoop.exec();处理的?
而GUI程序不会死锁是因为QNetworkAccessManager的操作在eventLoop.exec();里完成?
恩,您链接的文章里确实说了
“Calling the exec() function of QEventLoop can cause a deadlock in non GUI threads – signals sent by QNetworkRequest and such would not be accepted and so the quit() slot would never be activated.”
但是他的代码里其实也在non GUI线程用了eventLoop.exec();:
webfile::open->webfile::slotOpen->webfile::workerOpen->webfile::waitForConnect->eventLoop.exec();,
不过除了QNetworkAccessManager::finished外,他还连接了Timer超时和QNetworkReply::readyRead到QEventLoop::quit。
控制台程序开启另外的事件循环会导致主线程阻塞,主线程阻塞就不能接受别的信号,因此会发生死锁
你好,如果想写类似连锁超市收银系统,应该往哪块学习,是看QTcpserver和QTcpsocket这两个吗?还是看别的什么
这个具体要看你的需要了,
QTcpServer
和QTcpSocket
是 TCP 协议的封装,QNetworkAccessManager
是 HTTP 协议的封装,这要看你的系统使用的是哪个协议,还有很多其他的问题需要考虑也就是说,qt是能写出这样的系统,现在有准备要学习写,但是完全没有方向,之前有单独的超市收银,是用的http,如果是分布式的,会不会TCP更方便一些呢
“调用QEventLoop::exec()函数开始事件循环。此时界面就是被阻塞。”:这句话阻塞我觉得有待斟酌,阻塞会让人以为UI线程卡死、无响应,我认为QEventLoop::exec();调用是用自己的事件循环取代了UI线程的事件循环,让事件响应不再在UI线程的事件循环中而是在自己事件循环中,类似于QMessageBox::exec();
是的,就是你说的意思。个人理解这就是 UI 循环被阻塞