When using QTcpSocket
to 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.
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.
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).
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:
new connection!
32768 (or 16K or 48K)
The first part of the message is read, but the end isn't read anymore, as readyRead()
won't be called again.
My question is: what is the best way to be sure, this problem never occurs?
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 QTcpSocket
can't know that I'm calling the slot again, so the problem that readyRead()
isn't emitted can't happen anymore.
The reason why I have included the parameter bool selfCall
is that the slot which is called by the QTcpSocket
isn'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.
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?
The documentation of QIODevice::readyRead()
states:
readyRead()
is not emitted recursively; if you reenter the event loop or callwaitForReadyRead()
inside a slot connected to thereadyRead()
signal, the signal will not be reemitted.
Thus, make sure that you
QEventLoop
inside your slot,QApplication::processEvents()
inside your slot,QIODevice::waitForReadyRead()
inside your slot,QTcpSocket
instance within different threads.Now you should always receive all data sent by the other side.
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.