Search code examples
c++qtc++11qdatastream

QDataStream empty after call of QDataStream::writeRawData


I have a rather odd problem, using QDataStream, or at least it is odd to me, because I don't understand the behavior at all.

Of course my problem is from a big project, but I managed to reproduce that odd behavior with a minimal example, which I'll describe now.

I have two classes

  1. a binary data reader Reader and
  2. a binary data parser Parser.

The Reader reads data via a QTcpSocket, packs each received data chunk into a QByteArray and sends that array via Qt signal to Parser.

The Parser writes all received data chunks to its own QDataStream and shall parse packets from that stream afterwards.

The problem arises, when Parser writes the data from the received QByteArray to its QDataStream. The return value of QDataStream::writeRawData correctly returns the number of bytes written, but then QDataStream::atEnd returns true and QDataStream::device.bytesAvailable returns zero.

Why? Where is the data QDataStream::writeRawData claims to have written?

You can find the code at the and of this post.

Environment: Qt 5.9.1 (MSVC 2015, 32/64 bit) on Windows 7 Enterprise SP1 64 bit


Reader.h

#ifndef READER_H
#define READER_H

#include <QAbstractSocket>
#include <QByteArray>
#include <QDataStream>
#include <QHostAddress>
#include <QObject>

class Reader : public QObject
{
    Q_OBJECT

public:
    Reader(const QHostAddress ip, quint16 port);
    virtual ~Reader();

signals:
    void signalNewData(const QByteArray data);

private slots:
    void slotOnReadyRead();

private:
    QAbstractSocket *mSocket;
    QDataStream mStream;
};

#endif // READER_H

Reader.cpp

#include "reader.h"

#include <QTcpSocket>

Reader::Reader(const QHostAddress ip, quint16 port)
    : mSocket(new QTcpSocket(this))
    , mStream()
{
    mStream.setDevice(mSocket);
    mStream.setVersion(QDataStream::Qt_5_9);
    mStream.setByteOrder(QDataStream::LittleEndian);

    connect(mSocket, &QIODevice::readyRead, this, &Reader::slotOnReadyRead);

    mSocket->connectToHost(ip, port, QIODevice::ReadOnly);
}

Reader::~Reader()
{
    mSocket->disconnectFromHost();
    delete mSocket;
    mSocket = nullptr;
}

void Reader::slotOnReadyRead()
{
    mStream.startTransaction();

    quint64 availableBytesForReading = mStream.device()->bytesAvailable();
    QByteArray binaryDataBlock;
    char *tmp = new char[availableBytesForReading];
    mStream.readRawData(tmp, availableBytesForReading);
    binaryDataBlock.append(tmp, availableBytesForReading);
    delete[] tmp;
    tmp = nullptr;

    if (mStream.commitTransaction())
    {
        emit signalNewData(binaryDataBlock);
    }
}

Parser.h

#ifndef PARSER_H
#define PARSER_H

#include <QByteArray>
#include <QDataStream>
#include <QObject>

class Parser : public QObject
{
    Q_OBJECT

public:
    Parser();

public slots:
    void slotOnNewData(const QByteArray data);

private:
    QDataStream mStream;
};

#endif // PARSER_H

Parser.cpp

#include "parser.h"
#include <QDebug>

Parser::Parser()
    : mStream(new QByteArray(), QIODevice::ReadWrite)
{
    mStream.setVersion(QDataStream::Qt_5_9);
    mStream.setByteOrder(QDataStream::LittleEndian);
}

void Parser::slotOnNewData(const QByteArray data)
{
    const char *tmp = data.constData();
    int numberOfBytesWritten = mStream.writeRawData(tmp, data.length());

    qDebug() << "numberOfBytesWritten:" << numberOfBytesWritten << endl;
    qDebug() << "QDataStream::status():" << mStream.status() << endl;
    qDebug() << "QDataStream::atEnd():" << mStream.atEnd() << endl;
    qDebug() << "QDataStream::device.bytesAvailable():" << mStream.device()->bytesAvailable() << endl;
}

main.cpp

#include <QCoreApplication>
#include "reader.h"
#include "parser.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Reader *reader = new Reader(QHostAddress("<insert IP>"), <insertPort>);
    Parser *parser = new Parser();

    QObject::connect(&a, &QCoreApplication::aboutToQuit, reader, &QObject::deleteLater);
    QObject::connect(&a, &QCoreApplication::aboutToQuit, parser, &QObject::deleteLater);

    QObject::connect(reader, &Reader::signalNewData, parser, &Parser::slotOnNewData);

    return a.exec();
}

delete.pro Yes, I called my minimal example project 'delete' :'D

QT += core network
QT -= gui

CONFIG += c++11

TARGET = delete
CONFIG += console
CONFIG -= app_bundle

TEMPLATE = app

SOURCES += main.cpp \
    reader.cpp \
    parser.cpp

# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

HEADERS += \
    reader.h \
    parser.h

Solution

  • The problem is not that there is no data but that your current position is at the end of the data. Use device() to retrieve the QBuffer which it used to wrap your QByteArray and reset the position of that object.

    mStream.device()->reset()