0

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.

TyTu
  • 5
  • 3
  • *I don't understand why this is happening,* -- `QString Name;` is `QString` a trivially-copyable type? If not, then none of this `stream.readRawData( reinterpret_cast( wektor.data() ) , rozmiarSampla * ileSampli );` will work. The simple reason why it won't work is if the `QStrinq` contained a million characters, what will the value of `rozmiarSampla * ileSampli ` be? Given that, how do you expect to process over a million characters if `rozmiarSampla * ileSampli ` is so tiny? – PaulMcKenzie Jan 05 '23 at 17:50
  • You need to *serialize* the data before sending it. I suspect Qt have some nice serialization library in its framework. – Some programmer dude Jan 05 '23 at 17:51
  • 2
    BTW, the issue has really nothing to do with `qt` or even sockets, and everything to do with proper object serialization. The same issue would have happened if you tried to read/write the data to a binary file using the same techniques. As mentioned, you should serialize the *data* that the object represents. – PaulMcKenzie Jan 05 '23 at 17:55
  • Binary read and write functions literally write an object. If the object is a pointer to data and a length of the data write literally writes the address value of the pointer and the value of the length. That's pretty much useless to you as the pointed-at data is not written, and the location of the data on the source is at best not useful at the destination. Usually using the address at the destination is fatal because the destination either isn't using that memory location (crash) or some totally unrelated variables are using that memory (no one knows knows what will happen). – user4581301 Jan 05 '23 at 18:25
  • Not sure if it qualifies as a dupe per se but... you might want to look at ["Serialization with Qt"](https://stackoverflow.com/questions/2570679/serialization-with-qt). – G.M. Jan 05 '23 at 18:59

1 Answers1

0

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;
Dean Johnson
  • 1,682
  • 7
  • 12