可以使用 `waitForReadyRead()` 而不是为 `readyRead()` 信号创建一个插槽吗?

Posted

技术标签:

【中文标题】可以使用 `waitForReadyRead()` 而不是为 `readyRead()` 信号创建一个插槽吗?【英文标题】:Is it OK to use `waitForReadyRead()` instead of creating a slot for `readyRead()` signal? 【发布时间】:2017-06-14 12:21:44 【问题描述】:

使用 Qt(包括带有 MinGW 的 Windows)编写跨平台应用程序。为了从 SSL 套接字读取数据,我正在创建一个单独的线程。由于历史原因,该线程存在,因为之前该应用程序是使用 C 套接字/ssl/crypto 库编写的。现在所有这些都被 Qt Network 库所取代。

对于阻塞线程,waitForReadyRead(milliseconds) 似乎是更好的选择。现在根据 Qt 层次结构:

QIODevice
   |
QAbstractSocket
   |
QTcpSocket
   |
QSslSocket

QAbscractSocket::waitForReadyRead() 的文档建议:

注意:此功能在 Windows 上可能会随机失败。如果您的软件将在 Windows 上运行,请考虑使用事件循环和 readyRead() 信号。

但是QIODevice::waitForReadyRead()中没有提到类似的警告。

问题QSslSocket::waitForReadyRead() 是否始终可用于所有平台?


为什么我不使用 readyRead() 信号?出于某种奇怪的原因,如果我使用 readyRead() 插入某个方法然后它不会被调用。此外, QSslSocket::write() 也不起作用,否则与上述方法一起工作。由于我的代码很复杂,我无法在这里展示它。

【问题讨论】:

您声明您正在使用线程。如果readyRead 发射器和接收器在不同的线程上,你确定接收器的线程有一个活动的事件循环吗?顺便说一句,我不禁觉得使用waitForReadyRead 只会暂时掩盖真正潜在问题的症状。 @G.M.最初只有 1 个线程建立 TLS 连接并与服务器交换几个自定义消息(读取和写入)。只有当这成功时,我才开始一个新的循环用于独占读取目的。对于readyRead(),我确信我这边一定存在编码/理解错误,才能让它发挥作用。但是,如果我必须使用 waitFor...() 方法,那么我当前的设计可以自行运行。唯一担心的是它的 Windows 实现。另一个较小的问题是,有时 Qt readyRead() 在多个连续数据消息之后发出。 socket-select() 在这里更快。 【参考方案1】:

对于您的问题:是的,您可以使用QSslSocket::waitForReadyRead(),但在 Widows 上,即使数据到达套接字,它也会超时。因此,如果发生超时,您必须检查它是超时还是方法失败。检查很简单,只要QAbstractSocket::bytesAvailable() > 0 则数据已准备好读取,否则超时。

当您使用小超时并且您的应用程序对延迟不敏感(例如,温度传感器和具有温度历史记录的云之间的通信)时,这种方法是可以的。但是如果你不能接受任何不必要的延迟,那么你应该使用信号/槽接口。

有关更多信息,您可以查看 Qt 错误跟踪器上的 bug report。

【讨论】:

【参考方案2】:

根据您的问题。 QIODevice 的实现除了返回 false 什么都不做。因此,不需要有时会失败的暗示。 QAbstractSocket 的实现在内部调用称为“nativeSelect”的东西,然后根据您运行的操作系统将其定向到相应的方法。对于 Windows,select 实现有时似乎返回一个否定的 false。 但这不应该对您造成伤害,因为您应该从下一次调用 waitForReadyRead() 中获得可用数据的提示。 QSslSocket 的 waitForReadyRead() 内部使用来自一些 SSL 检查的 QAbstactSocket 的实现装置。

关于信号和插槽的问题。 我刚接触 Qt 时犯的一个错误是,我试图在启动 MainLoop 之前通过调用 QApplication::exec() 或其他方法来发出信号。 如果没有运行循环,信号槽机制将无法工作。

希望你能从中得到一些提示。

问候

【讨论】:

【参考方案3】:

问题可能是使用资源。

当您使用waitForReady* 时,您会为每个线程创建一个套接字(否则您会遇到奇怪的错误)。 现在的问题是你有多少个插座?如果它取决于运行时数据,您可能不知道。 一些嵌入式系统对可能影响您的应用程序的踏板数量有限制,而 IMO 这只是可能影响此类实施的限制。

这部分问题:

为什么我不使用 readyRead() 信号?出于某种奇怪的原因,如果我 使用 readyRead() 插入一些方法,然后它不会被调用。 此外, QSslSocket::write() 也不起作用,它有效 否则采用上述方法。由于我的代码的复杂性,我 无法在这里展示。

看起来很可疑。 我从来没有见过有人有类似的问题。也许您的代码的某些部分阻塞了事件循环?

【讨论】:

我已经编辑了可疑部分,因为这是我的编码错误。目前我只使用 1 个套接字。可能是 Qn 现在更特定于 wait..() 方法。【参考方案4】:

虽然这不是 Qn 的确切答案,但我发布了waitForReadyRead() 的可能实现,它可以在本地事件循环下使用。

class MySslSocket : QSslSocket

  Q_OBJECT 
public:
  virtual
  bool
  waitForReadyRead (int milliseconds) override final
  
    QEventLoop eventLoop;
    QTimer timer;
    connect(this, SIGNAL(readyRead()), &eventLoop, SLOT(quit()));
    connect(&timer, SIGNAL(timeout()), &eventLoop, SLOT(quit()));
    timer.setSingleShot(true);
    timer.start(milliseconds);

    eventLoop.exec();
    return timer.isActive();
  
;

这可以专门用于 Windows,也可以普遍用于所有平台。

【讨论】:

这当然不是一个好的解决方案。您可以参考这个question了解更多详情。

以上是关于可以使用 `waitForReadyRead()` 而不是为 `readyRead()` 信号创建一个插槽吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用QProcess,是不是需要在waitForFinished之后调用waitForReadyRead?

子线程中的 waitForReadyRead() 冻结 GUI 线程

QSerialPort readyRead 信号仅在应用程序被 waitForReadyRead() 阻塞时发出

没有信号/插槽的 QtcpSocket

QTcpSocket如何返回QString

QTcpSocket write 发不出去