当使用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?