如何确保readyRead()从信号与QTcpSocket不能错过?(How to make sur

2019-08-31 06:45发布

当使用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() -检查(甚至放置/其他检查的功能已完全结束并没有解决这个问题)。

并确保能够重现问题

当然这个问题很少发生,我坚持先执行插槽的,我甚至会添加人工超时,以确保出现问题:

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字节的数据。 这是发生了什么:

新的连接!
32768(或16K或48K)

该消息的第一部分是阅读,但到底是不是再阅读,如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() ISN”吨射出。

这真的是解决我的问题的最佳解决方案? 这是问题的存在,在Qt的设计错误还是我失去了一些东西?

Answer 1:

简答

该文件中QIODevice::readyRead()规定:

readyRead()不是递归射出; 如果重新输入事件循环或呼叫waitForReadyRead()连接到一个槽内readyRead()信号时,信号将不被重新发射。

因此,请确保您

  • 实例化一个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()已完成信号(换句话说,什么时候就递归)。

和递归应该只在上面列出的情况下是不可能的。



Answer 2:

我觉得这个题目中提到的情况下具有不同的方式工作的两大案件,但总体上QT没有这个问题在所有的,我会尝试以下原因来解释。

第一种情况:单线程应用程序。

Qt使用select()系统调用的任何变化发生的或可用的操作轮询打开文件描述符。 在每个循环的Qt检查简单的说,如果任何打开的文件描述符有可供读取/关闭等的资料,因此对单线程应用程序的流程看起来像这样(代码部分简体)

int mainLoop(...) {
     select(...);
     foreach( descriptor which has new data available ) {
         find appropriate handler
         emit readyRead; 
     }
}

void slotReadyRead() {
     some code;
}

因此,如果新的数据到达时程序还在里面slotReadyRead ..说实话没有什么特别的东西会happend。 操作系统将缓冲数据,并尽快控制进入下一执行select()操作系统将通知软件,有可用于特定的文件句柄数据。 它可以在完全同样的方式为TCP套接字/文件等

我可以成像在哪里(在slotReadyRead很长的延迟的情况下和大量的数据来的),你可以体验到OS FIFO缓冲区内的超限(例如用于串行端口)的情况,但有更多的是与一个糟糕的软件设计而不是QT或操作系统的问题。

你应该看看像readyRead插槽像一个中断处理程序,并保持其只在取功能填补你的内部缓冲区,而处理应该在单独的线程或同时应用程序来完成闲置等原因在于,一般来说,任何这样的应用是逻辑质量服务体系,如果花费更多的时间在服务一个请求,那么两个请求之间的时间间隔是队列无论如何都会溢出。

第二个方案:多线程应用程序

其实这种情况并不多,从1不同)期待您应该设计权在每个线程会发生什么。 如果你保持主回路与wighted光“伪中断处理程序”,你是绝对精细,并保持处理逻辑在其他线程,但这种逻辑应该用自己的预读取缓存工作,而然后用了QIODevice。



Answer 3:

这个问题很有意思。

在我的节目中与QTcpSocket的使用是非常密集。 所以我写了整个图书馆,打破传出数据成包一个头,数据标识,包装索引号和最大尺寸,而当下一块数据到来时,我确切地知道它所属。 即使我错过了什么,当下一个readyRead到来时,接收器读取所有和撰写正确接收数据。 如果你的程序之间的通信是不那么激烈,你可以做同样的,但计时器(这是不是非常快,但是解决了这个问题。)

关于您的解决方案。 我不认为这是更好,然后这样的:

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

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

两种方法的问题是代码离开时隙之后的权利,但从发射信号返回之前。

你也可以用连接Qt::QueuedConnection



Answer 4:

这里有方法让整个文件,但使用QNetwork API的其他一些地方的一些例子:

http://qt-project.org/doc/qt-4.8/network-downloadmanager.html

http://qt-project.org/doc/qt-4.8/network-download.html

这些例子表明一个更强有力的方式来处理TCP数据,而当缓冲区已满,且具有较高级别的API处理更好的错误。

如果你仍然想使用较低级别的API,这里是一个伟大的方式来处理的缓冲区后:

里面你readSocketData()做这样的事情:

if (bytesAvailable() < 256)
    return;
QByteArray data = read(256);

http://www.qtcentre.org/threads/11494-QTcpSocket-readyRead-and-buffer-size

编辑:如何用QTCPSockets互动附加例子:

http://qt-project.org/doc/qt-4.8/network-fortuneserver.html

http://qt-project.org/doc/qt-4.8/network-fortuneclient.html

http://qt-project.org/doc/qt-4.8/network-blockingfortuneclient.html

希望帮助。



Answer 5:

如果QProgressDialog应而从插座接收数据显示在它仅在任何工作QApplication::processEvents()被发送(例如,通过QProgessDialog::setValue(int)梅索德)。 这当然导致的损失readyRead如上所述的信号。

所以我的解决办法是一个while循环包括processEvents命令,例如:

void slot_readSocketData() {
    while (m_pSocket->bytesAvailable()) {
        m_sReceived.append(m_pSocket->readAll());
        m_pProgessDialog->setValue(++m_iCnt);
    }//while
}//slot_readSocketData

如果槽被调用一次的任何附加readyRead信号可以忽略不计,因为bytesAvailable()总是返回的实际数量后processEvents调用。 只有在while循环结束流的暂停。 但在以后的readReady不误,并再次启动它。



Answer 6:

我有同样的问题与readyRead插槽的时候了。 我不同意接受的答案; 它并没有解决问题。 利用信息bytesAvailable为Amartel描述是唯一可靠的解决方案,我发现。 Qt的:: QueuedConnection没有效果。 在下面的例子中,我反序列化的自定义类型,所以很容易预测的最小字节大小。 它从来不会错过的数据。

void MyFunExample::readyRead()
{
    bool done = false;

    while (!done)
    {

        in_.startTransaction();

        DataLinkListStruct st;

        in_ >> st;

        if (!in_.commitTransaction())
            qDebug() << "Failed to commit transaction.";

        switch (st.type)
        {
        case  DataLinkXmitType::Matrix:

            for ( int i=0;i<st.numLists;++i)
            {
                for ( auto it=st.data[i].begin();it!=st.data[i].end();++it )
                {
                    qDebug() << (*it).toString();
                }
            }
            break;

        case DataLinkXmitType::SingleValue:

            qDebug() << st.value.toString();
            break;

        case DataLinkXmitType::Map:

            for (auto it=st.mapData.begin();it!=st.mapData.end();++it)
            {
                qDebug() << it.key() << " == " << it.value().toString();
            }
            break;
        }

        if ( client_->QIODevice::bytesAvailable() < sizeof(DataLinkListStruct) )
            done = true;
    }
}   


文章来源: How to make sure that readyRead() signals from QTcpSocket can't be missed?