C++ 如何确保来自QTcpSocket的readyRead()信号能够';你不会错过吧?

C++ 如何确保来自QTcpSocket的readyRead()信号能够';你不会错过吧?,c++,qt,qtcpsocket,C++,Qt,Qtcpsocket,使用qtcsocket接收数据时,要使用的信号是readyRead(),表示新数据可用。 但是,当您在相应的插槽实现中读取数据时,不会发出额外的readyRead()。 这可能是有意义的,因为您已经在函数中读取所有可用数据 问题描述 但是,假设此插槽的以下实现: void readSocketData() { datacounter += socket->readAll().length(); qDebug() << datacounter; } 然而,我们还

使用
qtcsocket
接收数据时,要使用的信号是
readyRead()
,表示新数据可用。 但是,当您在相应的插槽实现中读取数据时,不会发出额外的
readyRead()
。 这可能是有意义的,因为您已经在函数中读取所有可用数据

问题描述 但是,假设此插槽的以下实现:

void readSocketData()
{
    datacounter += socket->readAll().length();
    qDebug() << datacounter;
}
然而,我们还没有解决这个问题。数据仍然可能在
socket->bytesavable()
-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();
}
由于我不直接调用插槽,而是使用
QTimer::singleShot()
,因此我假设
qtcsocket
无法知道我再次调用插槽,因此不再发出
readyRead()
的问题

我之所以包含参数
bool selfCall
,是因为不允许
qtcsocket
调用的插槽提前退出,否则同样的问题可能再次发生,即数据恰好在错误的时刻到达,并且不会发出
readyRead()

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

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

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

如果您仍然想使用较低级别的api,下面是一篇文章,其中介绍了一种处理缓冲区的好方法:

readSocketData()中执行以下操作:

void readSocketData()
{
    while(socket->bytesAvailable())
        datacounter += socket->readAll().length();
    qDebug() << datacounter;
}
if (bytesAvailable() < 256)
    return;
QByteArray data = read(256);
if(字节可用()<256)
返回;
QByteArray数据=读取(256);

编辑:有关如何与QTCPSocket交互的其他示例:


希望有帮助。

这个问题很有趣

在我的程序中,QTcpSocket的使用非常密集。所以我编写了整个库,它将传出的数据分解成带有标题、数据标识符、包索引号和最大大小的包,当下一个数据块出现时,我确切地知道它属于哪里。即使我错过了什么,当下一个
readyRead
到来时,接收器也会读取所有数据并正确地合成接收到的数据。如果您的程序之间的通信不是很激烈,您也可以这样做,但是使用计时器(虽然不是很快,但可以解决问题)

关于你的解决方案。我不认为这比这个更好:

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

        QEventLoop loop;
        QTimer::singleShot(1000, &loop, SLOT(quit()));
        loop.exec();
    }
}
void readSocketData()
{
while(套接字->字节可用()
{
datacounter+=socket->readAll().length();

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

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

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

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

我可以想象这样的情况(如果慢速读取延迟很长,并且有大量数据进入),您可能会在操作系统FIFO缓冲区(例如串行端口)中遇到超限,但这更多地与糟糕的软件设计有关,而不是与QT或操作系统问题有关

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

第二种场景:多线程应用程序

事实上,这个场景与1)期望您应该正确设计在每个线程中发生的事情没有太大区别。如果您使用轻量级的“伪中断处理程序”保持主循环,您将完全可以在其他线程中保持处理逻辑,但此逻辑应该使用您自己的预取缓冲区,而不是QIODevice。

简短回答
QIODevice::readyRead()的

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

因此,请确保

  • 不要在插槽中实例化
    QEventLoop
  • 不要在插槽内调用
    QApplication::processEvents()
  • 不要在插槽中调用
    QIODevice::waitForReadyRead()
  • 不要在不同线程中使用相同的
    QTcpSocket
    实例
现在您应该始终收到所有<
void readSocketData()
{
    while(socket->bytesAvailable())
    {
        datacounter += socket->readAll().length();
        qDebug() << datacounter;

        QEventLoop loop;
        QTimer::singleShot(1000, &loop, SLOT(quit()));
        loop.exec();
    }
}
int mainLoop(...) {
     select(...);
     foreach( descriptor which has new data available ) {
         find appropriate handler
         emit readyRead; 
     }
}

void slotReadyRead() {
     some code;
}
// Only emit readyRead() when not recursing.
if (!emittedReadyRead && channel == currentReadChannel) {
    QScopedValueRollback<bool> r(emittedReadyRead);
    emittedReadyRead = true;
    emit q->readyRead();
}
void slot_readSocketData() {
    while (m_pSocket->bytesAvailable()) {
        m_sReceived.append(m_pSocket->readAll());
        m_pProgessDialog->setValue(++m_iCnt);
    }//while
}//slot_readSocketData
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;
    }
}