当使用QTcpSocket接收数据时,要使用的信号是readyRead(),表示有新数据可用。
但是,当您在相应的插槽实现中读取数据时,将不会发出其他readyRead()
正如您已经在该函数中那样,在其中读取所有可用数据可能是有道理的。

问题描述

但是,假定此插槽的以下实现:

void readSocketData()
{
    datacounter += socket->readAll().length();
    qDebug() << datacounter;
}

如果在调用readAll()之后但离开插槽之前有一些数据到达该怎么办?
如果这是另一个应用程序发送的最后一个数据包(或一段时间内至少是最后一个),该怎么办?
不会发射其他信号,因此您必须确保自己读取所有数据。

最小化问题的一种方法(但不能完全避免)

当然我们可以这样修改插槽:
void readSocketData()
{
    while(socket->bytesAvailable())
        datacounter += socket->readAll().length();
    qDebug() << datacounter;
}

但是,我们尚未解决问题。仍然有可能数据仅在socket->bytesAvailable() -check之后到达(甚至将/另一个检查放在函数的绝对末尾也不能解决此问题)。

确保能够重现问题

因为这个问题当然很少发生,所以我坚持使用该插槽的第一个实现,甚至还要添加一个人工超时,以确保出现此问题:
void readSocketData()
{
    datacounter += socket->readAll().length();
    qDebug() << datacounter;

    // wait, to make sure that some data arrived
    QEventLoop loop;
    QTimer::singleShot(1000, &loop, SLOT(quit()));
    loop.exec();
}

然后,我让另一个应用程序发送100,000个字节的数据。
这是发生了什么:



消息的第一部分已读取,但末尾不再读取,因为不会再次调用readyRead()

我的问题是:什么才是最好的保证,这个问题永远不会发生?

可能的解决方案

我想出的一种解决方案是,再次在末尾再次调用同一插槽,并在插槽的开头进行检查,是否还有其他数据要读取:
void readSocketData(bool selfCall) // default parameter selfCall=false in .h
{
    if (selfCall && !socket->bytesAvailable())
        return;

    datacounter += socket->readAll().length();
    qDebug() << datacounter;

    QEventLoop loop;
    QTimer::singleShot(1000, &loop, SLOT(quit()));
    loop.exec();

    QTimer::singleShot(0, this, SLOT(readSocketDataSelfCall()));
}

void readSocketDataSelfCall()
{
    readSocketData(true);
}

因为我不直接调用插槽,而是使用QTimer::singleShot(),所以我假设QTcpSocket不知道我再次调用该插槽,因此不再发出readyRead()的问题不再发生。

我之所以包含参数bool selfCall的原因是,不允许QTcpSocket调用的插槽更早退出,否则会再次发生相同的问题,即数据恰好在错误的时间到达,并且readyRead()没有发出。

这真的是解决我的问题的最佳解决方案吗?
这个问题的存在是Qt中的设计错误还是我错过了一些东西?

最佳答案

简短答案
QIODevice::readyRead()documentation指出:



因此,请确保您

  • 不要实例化插槽
  • 中的QEventLoop
  • 不要在您的广告位
  • 中调用QApplication::processEvents()
  • 不要在您的广告位
  • 中调用QIODevice::waitForReadyRead()
  • 不要在不同线程中使用相同的QTcpSocket实例。

  • 现在,您应该始终收到对方发送的所有数据

    背景

    readyRead() 发出QAbstractSocketPrivate::emitReadyRead()信号,如下所示:
    // Only emit readyRead() when not recursing.
    if (!emittedReadyRead && channel == currentReadChannel) {
        QScopedValueRollback<bool> r(emittedReadyRead);
        emittedReadyRead = true;
        emit q->readyRead();
    }
    

    一旦emittedReadyRead块超出范围(由false完成),if变量就会回滚到QScopedValueRollback。因此,错过readyRead()信号的唯一机会是当控制流在
    之前再次达到if条件时,最后一个readyRead()信号的处理已完成(换句话说,当有递归时)。

    并且仅在上述情况下才可以进行递归。

    关于c++ - 如何确保不会遗漏来自QTcpSocket的readyRead()信号?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/16023533/

    10-11 15:14