Read from QTcpSocket using QDataStream
Solution 1
I reworked the code from @Marek's idea and created 2 classes - BlockReader and BlockWriter:
Sample usage:
// Write block to the socket.
BlockWriter(socket).stream() << QDir("C:/Windows").entryList() << QString("Hello World!");
....
// Now read the block from the socket.
QStringList infoList;
QString s;
BlockReader(socket).stream() >> infoList >> s;
qDebug() << infoList << s;
BlockReader:
class BlockReader
{
public:
BlockReader(QIODevice *io)
{
buffer.open(QIODevice::ReadWrite);
_stream.setVersion(QDataStream::Qt_4_8);
_stream.setDevice(&buffer);
quint64 blockSize;
// Read the size.
readMax(io, sizeof(blockSize));
buffer.seek(0);
_stream >> blockSize;
// Read the rest of the data.
readMax(io, blockSize);
buffer.seek(sizeof(blockSize));
}
QDataStream& stream()
{
return _stream;
}
private:
// Blocking reads data from socket until buffer size becomes exactly n. No
// additional data is read from the socket.
void readMax(QIODevice *io, int n)
{
while (buffer.size() < n) {
if (!io->bytesAvailable()) {
io->waitForReadyRead(30000);
}
buffer.write(io->read(n - buffer.size()));
}
}
QBuffer buffer;
QDataStream _stream;
};
BlockWriter:
class BlockWriter
{
public:
BlockWriter(QIODevice *io)
{
buffer.open(QIODevice::WriteOnly);
this->io = io;
_stream.setVersion(QDataStream::Qt_4_8);
_stream.setDevice(&buffer);
// Placeholder for the size. We will get the value
// at the end.
_stream << quint64(0);
}
~BlockWriter()
{
// Write the real size.
_stream.device()->seek(0);
_stream << (quint64) buffer.size();
// Flush to the device.
io->write(buffer.buffer());
}
QDataStream &stream()
{
return _stream;
}
private:
QBuffer buffer;
QDataStream _stream;
QIODevice *io;
};
Solution 2
The problem is a bit more serious. Socket can receive data in chunks, so even if you will wait for waitForReadyRead
it can fail since there is not enough data to immediately read some object.
To solve this problem you have to send a size of data first then actual data.
Send code:
QByteArray block;
QDataStream sendStream(&block, QIODevice::ReadWrite);
sendStream << quint16(0) << str;
sendStream.device()->seek(0);
sendStream << (quint16)(block.size() - sizeof(quint16));
tcpSocket->write(block);
On receiver you have to wait until size of available data is meets requirement. Receiver code looks more or less like that:
void SomeClass::slotReadClient() { // slot connected to readyRead signal of QTcpSocket
QTcpSocket *tcpSocket = (QTcpSocket*)sender();
QDataStream clientReadStream(tcpSocket);
while(true) {
if (!next_block_size) {
if (tcpSocket->bytesAvailable() < sizeof(quint16)) { // are size data available
break;
}
clientReadStream >> next_block_size;
}
if (tcpSocket->bytesAvailable() < next_block_size) {
break;
}
QString str;
clientReadStream >> str;
next_block_size = 0;
}
}
small update, based on documentation it is possible to read QString without adding extra size information, since QString passed to QDataStream contains size information. Size can be verified like that:
void SomeClass::slotReadClient() { // slot connected to readyRead signal of QTcpSocket
QTcpSocket *tcpSocket = (QTcpSocket*)sender();
while(true) {
if (tcpSocket->bytesAvailable() < 4) {
break;
}
char buffer[4]
quint32 peekedSize;
tcpSocket->peek(buffer, 4);
peekedSize = qFromBigEndian<quint32>(buffer); // default endian in QDataStream
if (peekedSize==0xffffffffu) // null string
peekedSize = 0;
peekedSize += 4;
if (tcpSocket->bytesAvailable() < peekedSize) {
break;
}
// here all required for QString data are available
QString str;
QDataStream(tcpSocket) >> str;
emit stringHasBeenRead(str);
}
}
Solution 3
You can call the QTCPSocket::waitForReadyRead function, which will block until data is available, or connect to the readyRead() signal and when your slot is called, then read from the stream.
sashoalm
Updated on July 21, 2022Comments
-
sashoalm almost 2 years
I need to send binary data through a
QTcpSocket
. I was thinking about usingQDataStream
, but I've encountered a problem - it silently fails if no data has arrived at the time I try to read.For example if I have this code:
QString str; stream >> str;
It will fail silently if no data is currently there in the socket. Is there a way to tell it to block instead?
-
sashoalm over 10 yearsYes, but I can't know how many bytes I need. Suppose I have serialized a QString, how do I know how many bytes I need available? How long is the QString? QDataStream takes care of those details. This approach defeats the purpose of using QDataStream in the first place. Btw I had a paragraph explaining why it's not a good solution in my question, but decided to remove it to keep my question brief - I don't like to enumerate all the ideas that are not good.
-
sashoalm over 10 yearsThanks, this seems to be the right way. I'm reworking the code to make a class
Block
, that automates the writing of the size of the block in its constructor/destructor, and will post the code shortly. -
TheDarkKnight over 10 years@MarekR beat me to it; prepending the size of the packet and checking for the size on the receiving socket is definitely the right way to go.
-
sashoalm over 10 yearsOK, I finished writing the classes, I've posted it as an answer.
-
TheDarkKnight over 10 yearsI like that, though I'd probably overload the stream operators of the classes, rather than return the internal _stream to the user, just to make it a little more elegant in its use.
-
Nicholas Johnson almost 7 yearsCan this by chance be used to transmit LARGE files?
-
Maxwell175 over 6 years@Nicholas I'd think so. As long as you split your large file(s) up into blocks. But all at once... probably not.