How to make sure that readyRead() signals from QTcpSocket can't be missed?

老子叫甜甜 提交于 2019-11-29 21:26:10

Short answer

The documentation of QIODevice::readyRead() states:

readyRead() is not emitted recursively; if you reenter the event loop or call waitForReadyRead() inside a slot connected to the readyRead() signal, the signal will not be reemitted.

Thus, make sure that you

  • don't instantiate a QEventLoop inside your slot,
  • don't call QApplication::processEvents() inside your slot,
  • don't call QIODevice::waitForReadyRead() inside your slot,
  • don't use the same QTcpSocket instance within different threads.

Now you should always receive all data sent by the other side.


Background

The readyRead() signal is emitted by QAbstractSocketPrivate::emitReadyRead() as follows:

// Only emit readyRead() when not recursing.
if (!emittedReadyRead && channel == currentReadChannel) {
    QScopedValueRollback<bool> r(emittedReadyRead);
    emittedReadyRead = true;
    emit q->readyRead();
}

The emittedReadyRead variable is rolled back to false as soon as the if block goes out of scope (done by the QScopedValueRollback). So the only chance to miss a readyRead() signal is when the control flow reaches the if condition again before the processing of the last readyRead() signal has finished (in other words, when there would be a recursion).

And a recursion should only be possible in the situations listed above.

I think scenario mentioned in this topic has two major cases which works differently, but in general QT doesn't have this problem at all and I will try to explain below why.

First case: Single threaded application.

Qt uses select() system call to poll open file descriptor for any change happened or operations available. Simple saying on every loop Qt checks if any of opened file descriptors have data available to read/closed etc. So on single threaded application flow looks like that (code part simplified)

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

void slotReadyRead() {
     some code;
}

So what will happend if new data arrived while program still inside slotReadyRead.. honestly nothing special. OS will buffer data, and as soon as control will return to next execute of select() OS will notify software that there are data available for particular file handle. It works in absolutely the same way for TCP sockets/files etc.

I can imaging situations where (in case of really long delays in slotReadyRead and a lot of data coming) you can experience an overrun within OS FIFO buffers (for example for serial ports) but that has more to do with a bad software design rather then QT or OS problems.

You should look on slots like readyRead like on a interrupt handlers and keep their logic only within fetch functionality which fills your internals buffers while processing should be done in separate threads or while application on idle etc.. Reason is that any such application in general is a mass service system and if it spends more time on serving one request then a time interval between two requests it's queue will overrun anyway.

Second scenario: multithreaded application

Actually this scenario is not that much differ from 1) expect that you should design right what happens in each of your threads. If you keep main loop with light wighted 'pseudo interrupt handlers' you will be absolutely fine and keep processing logic in other threads, but this logic should work with your own prefetch buffers rather then with QIODevice.

The problem is quite interesting.

In my program the usage of QTcpSocket is very intensive. So I've written the whole library, that breaks outgoing data into packages with a header, data identifier, package index number and maximum size, and when the next piece of data comes, I know exactly where it belongs to. Even if I miss something, when the next readyRead comes, the receiver reads all and compose received data correctly. If the communication between your programs is not so intense, you could do the same, but with timer (which is not very fast, but solves the problem.)

About your solution. I don't think it's better then this:

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

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

The problem of both methods is the code right after leaving the slot, but before returning from emitting the signal.

Also you could connect with Qt::QueuedConnection.

Here are some examples of ways to get the whole file, but using some other parts of the QNetwork API:

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

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

These examples show a stronger way to handle the TCP data, and when buffers are full, and better error handling with a higher level api.

If you still want to use the lower level api, here is a post with a great way to handle the buffers:

Inside your readSocketData() do something like this:

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

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

EDIT: Additional examples of how to interact with 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

Hope that helps.

If a QProgressDialog shall be shown while receiving data from a socket it only works if any QApplication::processEvents() are sent (e.g. by the QProgessDialog::setValue(int) methode). This of course leads to the loss of readyRead signals as mentioned above.

So my workaround was a while loop including the processEvents command such as:

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

If the slot is called once any additional readyRead signals can be ignored because the bytesAvailable() always returns the actual number after the processEvents call. Only on pausing of the stream the while loop ends. But then the next readReady is not missed and starts it again.

I had the same problem right away with the readyRead slot. I disagree with the accepted answer; it doesn't solve the problem. Using bytesAvailable as Amartel described was the only reliable solution I found. Qt::QueuedConnection had no effect. In the following example, I'm deserializing a custom type, so it's easy to predict a minimum byte size. It never misses data.

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;
    }
}   
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!