Read from Qtcpsocket Using Qdatastream

Read from QTcpSocket using QDataStream

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;
};

Using Qdatastream read data from socket and write into file

QDataStream is designed to provide platform independent data serialization.

For example, you want to save some float to a file (or to send it over tcp stream) in some binary format.
Then this float should be read from that file (or received from tcp) on another PC with different CPU and even different byte order (endianness).

So, QDataStream can be used for such task. It allows you to encode C++ basic datatypes and to decode the original values on any other platform.

If you just want to save binary data from a TCP stream as is to a binary file then you do not need QDataStream.
You can do it directly either synchronously blocking thread or asynchronously using readyRead() socket signal.

Example 1. Blocking socket for non-GUI thread

QFile file("out.bin");
if (!file.open(QIODevice::WriteOnly))
return;

char buffer[50];
forever {
int numRead = socket.read(buffer, 50);
// do whatever with array
file.write(buffer, numRead);

if (numRead == 0 && !socket.waitForReadyRead())
break;
}

Example 2. Asynchronous non-blocking socket

// This slot is connected to QAbstractSocket::readyRead()
void SocketClass::readyReadSlot()
{
while (!socket.atEnd()) {
QByteArray data = socket.read(100);
file.write(data);
}
}

Those examples are based on the documentation of QAbstractSocket Class (there you can find detailed explanation how it works).

Read and Send a file via QDataStream and QTcpSocket

content is not empty, but if you interpret it as a C-style 0-terminated string, it will appear to be empty.

When you write:

out.device()->seek(0);
out << static_cast<quint16>(content.size());

This will set the first two bytes of content to content.size() in big endian format (this is the default). So if content.size() is less than 255, the first byte of content.constData() will be 0 ('\0'). Any attempt to print constData() with a function that expects a C-style string will output nothing since your "string" starts with the "end-of-string" marker.

If you want to see the full contents of content, you should print all its chars separately and use something like hexdump to view the raw data.

Here's what I get if I do this instead of qDebug() << content.constData(); :

for (int i=0; i<content.size(); i++) {
std::cout << content.constData()[i];
}

Output when run (the file contains just 20 'a' chars):

 $ ./qt | hexdump -C
00000000 00 40 00 00 00 05 66 69 6c 65 00 00 00 00 19 2f |.@....file...../|
00000010 68 6f 6d 65 2f 71 74 2f 43 6c 69 65 6e 74 2f 66 |home/qt/Client/f|
00000020 69 6c 65 2e 74 78 74 00 00 00 00 14 61 61 61 61 |ile.txt.....aaaa|
00000030 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 |Read from Qtcpsocket Using QdatastreamRead from Qtcpsocket Using Qdatastream|
00000040

If I had used:

std::cout << content.constData();

There would have been no output because of that very first 0 char.

If your data is longer, and the size of content is bigger than 255, the first char will no longer be 0, but you'll print two characters of garbage and nothing else because Qt serializes QString (and most other types) by first writing its length (32bit here), then its contents. Since its in big endian notation, the first byte has a very high chance of being 0.

Annotated output:

00000000  00 40 00 00 00 05 66 69  6c 65 00 00 00 00 19 2f  |.@....file...../|
<u16> < str len > < str data > < str len > <
00000010 68 6f 6d 65 2f 71 74 2f 43 6c 69 65 6e 74 2f 66 |home/qt/Client/f|
str data...
00000020 69 6c 65 2e 74 78 74 00 00 00 00 14 61 61 61 61 |ile.txt.....aaaa|
str data > <data len > <
00000030 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 |Read from Qtcpsocket Using QdatastreamRead from Qtcpsocket Using Qdatastream|
data >

Read (many) values from QTcpSocket (fast)

Your code has couple mistakes.

You are doing direct reading from socket when in the same time you are using QDataStream. This can break stuff.

Also your code is assuming that your application will receive data in same chunks as it was sent by other end. You do not have such warranty! It may happen that you will receive chunk data which are ending in middle of your frame. It works just by pure luck or you are ignoring some bugs of your application.

This should go like this:

while(true)
if (mysock->waitForReadyRead()) // IMO doing such loop is terrible approach
// but this is Out of the scope of question, so ignoring that
{
while (true)
{
stream.startTransaction();
float result;
qint32 somedata
stream >> somedata >> result; // I do not know binary format your application is using

if (!in.commitTransaction())
break;

AddDataToModel(result, somedata);
}
}


Edit:

From comment:

Please correct me if I'm wrong, but if I want 2 bytes to be discarded I need to do "stream >> someint(2 byte) >> somefloat(4 byte)"? How can I handle many values in stream?

qint16 toBeDiscarded;
float value;
// note stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
// is needed to read float as 32 bit floating point number

stream >> toBeDiscarded >> value;
ProcessValue(value);

How to send QSqlQueryModel over QTcpSocket with QDatastream?

The purpose of any descendant of QAbstractItemModel is to adopt some data to views (widgets) which can present them.

So basically you are trying do something what is outside of design of this class. Its like trying to hit the nail with a shovel. It is possible, but dangerous and not recommended.

Just read debase data using QSqlQuery (use hammer) iterate over a rows read columns and store them in some serialization format. You can use QDataStream for that:

    QDataStream out{&socket};

QSqlQuery query{"SELECT country FROM artist"};
while (query.next()) {
out << query.value(0).toString();
}

It is possible to make this asynchronous.

How to read a QTcpSocket from R

If you take a look at how the Fortune Server Example is implemented, you can see that it uses QDataStream to serialize fortunes (QStrings) over the socket:

QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);

out << fortunes.at(qrand() % fortunes.size());

So, the question is reduced to "How does QDataStream serialize QStrings?", and this is answered extensively in the documentation page about serializing Qt data types. You can see that a QString's serialization looks like this:

  • If the string is null: 0xFFFFFFFF (quint32)
  • Otherwise: The string length in bytes (quint32) followed by the data in UTF-16

And this is exactly what you are seeing in your question. The first four bytes are the string length in bytes, and the "nulls" you are seeing later appear because of using UTF-16 encoding.



Is this the only way to write data to the socket? If I wanted to transmit values of type double would I have to convert them to QByteArray to transmit them in this fashion? Is there not some non-text way of transmitting data through a socket?

You can use any serialization format you like. QDataStream is widely used in Qt since it supports most Qt data types out of the box. This has nothing to do with using QByteArray, you can let QDataStream write to the socket directly. QDataStream is, actually, a binary format (non-text) as you can see. If want textual human-readable formats, you can use JSON.

But if you are aiming to send data from Qt to R using QDataStream, you'll have to write your QDataStream deserializer for R. I would recommend using some common data serialization that has implementations in C++ and R (in lieu of re-inventing the wheel). I believe JSON meets this criterion, and if you want to use a binary format, msgpack might be interesting for you, since it supports a lot of programming languages (including R and C++).



Related Topics



Leave a reply



Submit