c++qtnetworkingqtcpsocketqtcpserver

How to send vector with class by QTcpSocket


I tried to send vector with class from client to server. Data is sent between sockets, but when I want to write them to the console, the server crashes

#include <QCoreApplication>
#include <QTcpSocket>
#include <QDataStream>
#include "data.h"

//client

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

    std::vector< data > wektor;

    data asd;
    asd.setName("asd");

    wektor.push_back(asd);
    wektor.push_back(asd);

    for (data w : wektor){
        qDebug()<<w.getName();

    }


    quint16 rozmiarSampla = sizeof( wektor[0] );
    quint16 ileSampli = wektor.size();

    QTcpSocket socket;

    socket.connectToHost("127.0.0.1",9999);

    if( !socket.waitForConnected() )
        return 1;

    QDataStream stream(&socket);

    QString typ("Paczka z buforem");

    stream << typ << rozmiarSampla << ileSampli;

    stream.writeRawData( reinterpret_cast<const char*>( wektor.data() ) , rozmiarSampla * ileSampli );

    socket.flush();

    socket.waitForBytesWritten();

    return 0;
}

SERVER

#include <QCoreApplication>
#include <QTcpServer>
#include <QTcpSocket>
#include <QDataStream>
#include <QScopedPointer>
#include "data.h"
#include <iostream>
#include <string.h>
//serwer
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    std::vector< data > wektor;

    QString typ;
    quint16 rozmiarSampla;
    quint16 ileSampli;

    QTcpServer serwer;
    serwer.listen(QHostAddress::AnyIPv4,9999);

    if( !serwer.waitForNewConnection(30000) )
        return 1;

    QScopedPointer<QTcpSocket> socket{ serwer.nextPendingConnection() };

    if(! socket->waitForReadyRead() )
        return 1;

    QDataStream stream(socket.data());

    stream >> typ >> rozmiarSampla >> ileSampli;

    if( rozmiarSampla == sizeof( wektor[0] ) ) {
        wektor.resize(ileSampli);
        stream.readRawData( reinterpret_cast<char*>( wektor.data() ) , rozmiarSampla * ileSampli );
    } else {
        stream.skipRawData(rozmiarSampla * ileSampli);
    }

    qDebug() << "Typ: " << typ;
    qDebug() << "RozmiarSampla: " << rozmiarSampla;
    qDebug() << "IleSampli: " << ileSampli;

    qDebug()<<wektor.size();
    qDebug()<< wektor[0].getName();
    return a.exec();
}

DATA.H

#ifndef DATA_H
#define DATA_H
#include <QtCore>


class data
{
public:
    data();
    void setName(QString name);
    QString getName();
private:
    QString Name;
};

#endif // DATA_H

DATA.CPP

#include "data.h"
#include <QtCore>

data::data()
{
    this->Name = "null";
}

void data::setName(QString name)
{
    this->Name = name;
}

QString data::getName()
{
    return this->Name;
}

The server crashes when trying to print the name from the data class. I don't understand why this is happening, can someone explain to me what the error is? When I tried to send a vector that consisted of data, e.g. int, everything worked fine.


Solution

  • As suggested by a comment, the problem here is that QString is not a trivially copyable type.

    What does that mean? Well, here is a simple model of what QString looks like:

    class QString {
    public:
       // all of the methods
    private:
       // QString doesn't store the data directly, it stores a pointer to the string data
        Data* data;
    };
    

    reference: https://codebrowser.dev/qt5/qtbase/src/corelib/text/qstring.h.html#979

    So when you do stream.writeRawData( reinterpret_cast<const char*>( wektor.data() ) , rozmiarSampla * ileSampli ), you are copying the data contained in the QString... but what it contains is just a pointer! You are not copying the string data itself, just the pointer to the data. Of course, that pointer is nonsense on the other side of the socket.

    So how can you fix this? There are lots of ways, but I would suggest specializing the QDataStream stream operator for your type:

    // In your data class header file
    QDataStream& operator<<(QDataStream& stream, const data& d);
    QDataStream& operator>>(QDataStream& stream, data& d);
    
    // and in the cpp file
    QDataStream& operator<<(QDataStream& stream, const data& d) {
       stream << d.getName();
       return stream;
    }
    
    QDataStream& operator>>(QDataStream& stream, data& d) {
       QString name;
       stream >> name;
       d.setName(name);
       return stream;
    }
    

    QDataStream knows how to serialize QString when given a QString like this. What these specializations do is show QDataStream how to serialize data. Now with this, you can serialize your std::vector<data> to the data stream with something like:

    stream << wektor.size(); // serialize the number of instances
    for (const auto& d : wektor)
       stream << d; // serialize each data instance
    

    And you can deserialize them with something like:

    size_t wektorSize;
    stream >> wektorSize;
    
    std::vector<data> wektor{wektorSize};
    
    for (auto& d : wektor)
       stream >> d;