C++ 如何确保不会错过来自 QTcpSocket 的 readyRead() 信号?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16023533/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 20:00:30  来源:igfitidea点击:

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

c++qtqtcpsocket

提问by Misch

When using QTcpSocketto receive data, the signal to use is readyRead(), which signals that new data is available. However, when you are in the corresponding slot implementation to read the data, no additional readyRead()will be emitted. This may make sense, as you are already in the function, where you are reading all the data that is available.

当使用QTcpSocket以接收数据,该信号是使用readyRead(),这标志着新的数据是可用的。但是,当您在相应的插槽实现中读取数据时,不会readyRead()发出额外的信息。这可能是有道理的,因为您已经在函数中,您正在读取所有可用数据。

Problem description

问题描述

However assume the following implementation of this slot:

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

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

What if some data arrives after calling readAll()but before leaving the slot? What if this was the last data packet sent by the other application (or at least the last one for some time)? No additional signal will be emitted, so you have to make sure to read all the data yourself.

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

One way to minimize the problem (but not avoid it totally)

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

Of course we can modify the slot like this:

当然,我们可以像这样修改插槽:

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

However, we haven't solved the problem. It is still possible that data arrives just after the socket->bytesAvailable()-check (and even placing the/another check at the absolute end of the function doesn't solve this).

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

Making sure to be able to reproduce the problem

确保能够重现问题

As this problem of course happens very rarely, I stick to the first implementation of the slot, and I'll even add a an artificial timeout, to be sure that the problem occurs:

由于这个问题当然很少发生,我坚持使用插槽的第一个实现,我什至会添加一个人为的超时,以确保问题发生:

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();
}

I then let another application send 100,000 bytes of data. This is what happens:

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

new connection!
32768 (or 16K or 48K)

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

The first part of the message is read, but the end isn't read anymore, as readyRead()won't be called again.

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

My question is: what is the best way to be sure, this problem never occurs?

我的问题是:确定这个问题永远不会发生的最佳方法是什么?

Possible solution

可能的解决方案

One solution I came up with is calling the same slot again at the end again, and to check at the beginning of the slot, if there is any more data to read:

我想出的一个解决方案是在最后再次调用同一个插槽,并在插槽的开头检查是否还有更多数据要读取:

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);
}

As I don't call the slot directly, but use QTimer::singleShot(), I assume that the QTcpSocketcan't know that I'm calling the slot again, so the problem that readyRead()isn't emitted can't happen anymore.

由于我不直接调用插槽,而是使用QTimer::singleShot(),我假设QTcpSocket无法知道我再次调用插槽,因此readyRead()不会再发生未发出的问题。

The reason why I have included the parameter bool selfCallis that the slot which is called by the QTcpSocketisn't allowed to exit sooner, else the same problem can occur again, that data arrives exactly at the wrong moment and readyRead()isn't emitted.

我包含该参数的原因是不允许bool selfCall调用的插槽QTcpSocket早点退出,否则同样的问题可能会再次发生,数据恰好在错误的时刻到达并且readyRead()没有发出。

Is this really the best solution to solve my problem? Is the existence of this problem a design error in Qt or am I missing something?

这真的是解决我的问题的最佳解决方案吗?这个问题的存在是 Qt 中的设计错误还是我遗漏了什么?

采纳答案by emkey08

Short answer

简答

The documentationof QIODevice::readyRead()states:

文档QIODevice::readyRead()状态:

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.

readyRead()不会递归发出;如果您重新进入事件循环或waitForReadyRead()在连接到readyRead()信号的插槽内调用,则不会重新发送信号。

Thus, make sure that you

因此,请确保您

  • don'tinstantiate a QEventLoopinside your slot,
  • don'tcall QApplication::processEvents()inside your slot,
  • don'tcall QIODevice::waitForReadyRead()inside your slot,
  • don'tuse the same QTcpSocketinstance within different threads.
  • 不要QEventLoop在您的插槽内实例化 a ,
  • 不要QApplication::processEvents()在你的插槽里面打电话,
  • 不要QIODevice::waitForReadyRead()在你的插槽里面打电话,
  • 不要QTcpSocket在不同的线程中使用相同的实例。

Now you should always receive alldata sent by the other side.

现在你应该总是收到对方发送的所有数据。



Background

背景

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

readyRead()信号由发射QAbstractSocketPrivate::emitReadyRead()如下:

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

The emittedReadyReadvariable is rolled back to falseas soon as the ifblock goes out of scope (done by the QScopedValueRollback). So the only chance to miss a readyRead()signal is when the control flow reaches the ifcondition again beforethe processing of the last readyRead()signal has finished (in other words, when there would be a recursion).

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

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

并且只有在上面列出的情况下才能进行递归。

回答by evilruff

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.

我认为本主题中提到的场景有两种主要情况,它们的工作方式不同,但总的来说 QT 根本没有这个问题,我将在下面尝试解释原因。

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)

Qt 使用 select() 系统调用来轮询打开的文件描述符以了解发生的任何更改或可用的操作。在每个循环中简单地说 Qt 检查是否有任何打开的文件描述符有可供读取/关闭的数据等。所以在单线程应用程序流看起来像这样(代码部分简化)

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.

那么如果新数据到达时程序仍在 slotReadyRead 中会发生什么......老实说没什么特别的。操作系统将缓冲数据,一旦控制返回到下一次执行 select() 操作系统将通知软件有数据可用于特定文件句柄。对于 TCP 套接字/文件等,它的工作方式完全相同。

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.

我可以想象以下情况(在 slotReadyRead 中出现非常长的延迟和大量数据的情况下)您可能会遇到 OS FIFO 缓冲区(例如串行端口)中的溢出,但这更多地与糟糕的软件设计有关,而不是QT 或操作系统问题。

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.

您应该像在中断处理程序上一样查看 readyRead 之类的插槽,并将它们的逻辑仅保留在填充内部缓冲区的提取功能中,而处理应该在单独的线程中完成或在应用程序处于空闲状态时等。原因是任何此类应用程序通常都是一个大规模服务系统,如果它在服务一个请求上花费更多时间,那么两个请求之间的时间间隔无论如何它的队列都会溢出。

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.

实际上,这种情况与 1) 期望您应该正确设计每个线程中发生的事情并没有太大区别。如果您使用轻量级的“伪中断处理程序”保持主循环,那么您将绝对没问题,并在其他线程中保持处理逻辑,但此逻辑应与您自己的预取缓冲区一起使用,而不是与 QIODevice 一起使用。

回答by Amartel

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 readyReadcomes, 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.)

在我的程序中,QTcpSocket 的使用非常密集。因此,我编写了整个库,将传出数据分解为带有标头、数据标识符、包索引号和最大大小的包,当下一个数据到来时,我确切地知道它属于哪里。即使我错过了一些东西,当下一次readyRead到来时,接收器会读取所有内容并正确组合接收到的数据。如果你的程序之间的通信不是那么激烈,你可以做同样的事情,但使用计时器(它不是很快,但可以解决问题。)

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.

您也可以与Qt::QueuedConnection.

回答by phyatt

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

以下是获取整个文件的一些示例,但使用 QNetwork API 的其他部分:

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

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

http://qt-project.org/doc/qt-4.8/network-download.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.

这些示例展示了一种更强大的处理 TCP 数据的方法,以及当缓冲区已满时,以及使用更高级别的 api 进行更好的错误处理。

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

如果您仍然想使用较低级别的 api,这里有一个很好的方法来处理缓冲区:

Inside your readSocketData()do something like this:

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

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

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

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

EDIT: Additional examples of how to interact with QTCPSockets:

编辑:如何与 QTCPSockets 交互的其他示例:

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

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-fortuneclient.html

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

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

Hope that helps.

希望有帮助。

回答by landydoc

If a QProgressDialogshall 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 readyReadsignals as mentioned above.

如果在QProgressDialog从套接字接收数据时应显示 a,则仅QApplication::processEvents()在发送任何数据时才有效(例如通过QProgessDialog::setValue(int)方法)。这当然会导致readyRead如上所述的信号丢失。

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

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

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 readyReadsignals can be ignored because the bytesAvailable()always returns the actual number after the processEventscall. Only on pausing of the stream the while loop ends. But then the next readReadyis not missed and starts it again.

如果插槽被调用一次,任何额外的readyRead信号都可以被忽略,因为调用bytesAvailable()后总是返回实际数字processEvents。只有在流暂停时,while 循环才会结束。但是接下来readReady不会错过并重新开始。

回答by user761576

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.

我在 readyRead 插槽上立即遇到了同样的问题。我不同意接受的答案;它没有解决问题。使用 Amartel 描述的 bytesAvailable 是我找到的唯一可靠的解决方案。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;
    }
}