1

I'm writing a simple TCP based network application in Qt and wanted to use QDataStream and QByteArray to send data over the network. The problem is that when I'm putting data into QByteArray they are "zeroed". For example (a slot in MainWindow that is connected to timer timeout signal):

void MainWindow::SendPlayer1Data(){
  QByteArray block;
  QDataStream s(&block, QIODevice::OpenModeFlag::ReadWrite);
  QString h="hello";
  s<<h;
  qDebug() << "h:        " << data;
  qDebug() << "block:    " << QString(block); // equivalent to s >> h on receiving end
  qDebug() << "block[0]: " << int(block[0]);
}

h:         "hello"
block:     ""
block[0]:  0

I receive "hello" once at the beginning but after that I only get "". The same goes for qint32. Both client and server shows that QByteArray size is 14 bytes, so QDataStream writes data into that array, but it makes them 0 (it shows "" when I use s >> h and then use qDebug() << h)

William Miller
  • 9,839
  • 3
  • 25
  • 46
Kestrel
  • 29
  • 5

2 Answers2

1

The issue here is writing a QString directly to a stream that is expecting a QByteArray, consider the following

QByteArray block;
QDataStream s(&block, QIODevice::OpenModeFlag::ReadWrite);
QString h = "hello";
s << h;
qDebug() << block;

Which outputs

"\x00\x00\x00\n\x00h\x00""e\x00l\x00l\x00o"

So the data is there, it just isn't there how one might expect it. The easiest way to solve this is to create a QByteArray from a string encoded with UTF8 (or other encoding of your choice). This can trivially be done on the fly,

QByteArray block;
QDataStream s(&block, QIODevice::OpenModeFlag::ReadWrite);
QString h = "hello";
QByteArray data(h.toUtf8(), 5);
s << data;
qDebug() << block;

Which outputs

"\x00\x00\x00\x05hello"

Because when this QByteArray is sent through the QDataStream the length of the array and 3 NULL characters are prepended - the NULL characters are there in case the buffer is larger than a relatively small 5 (you can test that for yourself by passing a larger value - a small factor of 256 is most demonstrative - as the second parameter in the QByteArray constructor as this is the buffer length). But if you try to explicitly construct a QString (as s >> h does) from the NULL-commenced QByteArray it will create an empty string. To correct for this you can use QByteArray::remove() to remove the first 4 bytes like this

QByteArray block;
QDataStream s(&block, QIODevice::OpenModeFlag::ReadWrite);
QString h = "hello";
QByteArray data(h.toUtf8());
s << data;
qDebug() << QString::fromUtf8(block.remove(0, 4));

Which outputs

"hello"

Complete example

#include <qbytearray.h>
#include <qdatastream.h>
#include <qdebug.h>

int main() {
  QByteArray block;
  QDataStream s(&block, QIODevice::OpenModeFlag::ReadWrite);
  QString h = "hello";
  QByteArray data(h.toUtf8());
  s << data;
  qDebug() << QString::fromUtf8(block.remove(0, 4));
}
William Miller
  • 9,839
  • 3
  • 25
  • 46
  • Thank you very much for this solution it gets the job done. But doesn't it make using QDataStream pointless if I have to get rid of NULL bytes manually? Shouldn't I write to QByteArray directly to write exactly what I want? – Kestrel Dec 29 '18 at 13:43
  • @Kestrel the point of the `QDataStream` is to serialize the data, but writing a `QByteArray` over serial requires sending a length - consider that you can send data that is not `NULL` terminated, without the length of the buffer you will get undefined behavior. It might be possible to send one byte at a time without any framing using `QDataStream::writeRawData()`, but I couldn’t get that to work so I excluded it from my answer. – William Miller Dec 29 '18 at 18:19
0

Ok, I have figured it out. The problem was not the QByteArray or socket because, as @William Miller mentioned, the data was there. The problem was with QDataStream on client side - I decided to create a new QDataStream object every time the slot responsible for receiving data was called. This way I was able to pack data easily into QByteArray, send it and receive every time. The client function for receiving:

void ClientTcpHelper::ReceivePacket(){

if(socket.waitForReadyRead(20)){

    //qDebug()<<"Packet: "<<socket.peek(30)<<endl;
    qDebug()<<"Receiving packet!"<<endl;

    Data=socket.readAll();
    emit DataReceived();


}
else{
    //qDebug()<<"Failed to receive packet!"<<endl;

}}

and unpacking data to variables:

void ClientTcpHelper::UnpackData(){

stream=new QDataStream (&this->Data,QIODevice::OpenModeFlag::ReadWrite);
*stream>>h>>a>>b;
Data.clear();
delete stream;}

h,a and b are members of a class.

Unfortunately I can not explain why QDataStream need to be destroyed every time here in order to handle data as I wanted it from the beginning.

Kestrel
  • 29
  • 5