How to make sure that readyRead() signals from QTcpSocket can't be missed?
c++, qt, qtcpsocket
Solution
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.
Problem
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. 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. 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). 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: 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? 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 `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?