sending struct via Qt UDP - c++

I am trying to setup two way communication via the QUdpSocket. I am trying to send a struct consisting of a C++ Eigenvector and a double. I have tried serializing into a QByteArray as follows:
MyStruct toSend;
QByteArray buf;
QDataStream s(&buf, QIODevice::WriteOnly);
if (false) s.setByteOrder(QDataStream::LittleEndian);
std::string vec_str = eigenToStr(toSend.vec);
s << (double)toSend.test1 << QString(vec_str.c_str());
Where eigenToStr() converts the Eigenvector to a string.
However, I am unable to read the message on the other end. When I convert back to a string before sending the QByteArray, I get #ffffff. So I assume it's an issue with the QByteArray/QDataStream conversion.
I would appreciate any suggestions as to how I might serialize my struct so that I can send it via UDP.
Thanks!

QByteArray and QDataStream cooperate very well:). If you really want write the raw data to QDataStream , please use the method :
int QDataStream::writeRawData(const char * s, int len)
And personally I prefer to overload << way to write user data to QDataStream, such as
QDataStream& operator <<(QDataStream& out,MyStruct & data)
Then probably code looks as :
MyStruct toSend;
QByteArray buf;
QDataStream s(&buf, QIODevice::WriteOnly);
if (false) s.setByteOrder(QDataStream::LittleEndian);//false ?
s << toSend;

Related

How to use regular std::basic_istream::read, std::basic_ostream::write on a buffer of data in memory?

I have a QByteArray and want to use it like a regular text file. How can I use functions like std::basic_istream::read on it ?
QByteArray data type is close to a container, not a stream. You can't handle it like a text file unless you inherit std::streambuf class that handles QByteArray internally.
You can also consider converting std::stringstream to QByteArray if you don't care of performance,
std::stringstream ss;
// write to ss ...
.
.
QByteArray qdata = QByteArray::fromStdString(ss.str());
for read operation,
QByteArray qdata; // consider qdata is already filled with data.
std::stringstream ss(qdata.toStdString());

Using stream to treat received data

I am receiving messages from a socket.
The socket is packed within a header (that is basically the size of the message) and a footer that is a crc (a kind of code to check if the message is not corrupted)
So, the layout is something like :
size (2 bytes) | message (240 bytes) | crc (4 byte)
I wrote a operator>>
The operator>> is as following :
std::istream &operator>>(std::istream &stream, Message &msg) {
std::int16_t size;
stream >> size;
stream.read(reinterpret_cast<char*>(&msg), size);
// Not receive enough data
if (stream.rdbuf()->in_avail() < dataSize + 4) {
stream.setstate(std::ios_base::failbit);
return stream;
}
std::int16_t gotCrc;
stream >> gotCrc;
// Data not received correctly
if(gotCrc != computeCrc(msg)) {
stream.setstate(std::ios_base::failbit);
}
return stream;
}
The message can arrive byte by byte, or can arrive totally. We can even receive several messages in once.
Basically, what I did is something like this :
struct MessageReceiver {
std::string totalDataReceived;
void messageArrived(std::string data) {
// We add the data to totaldataReceived
totalDataReceived += data;
std::stringbuf buf(totalDataReceived);
std::istream stream(&buf);
std::vector<Message> messages(
std::istream_iterator<Message>(stream),
std::istream_iterator<Message>{});
std::for_each(begin(messages), end(messages), processMessage);
// +4 for crc and + 2 for the size to remove
auto sizeToRemove = [](auto init, auto message) {return init + message.size + 4 + 2;};
// remove the proceed messages
totalDataReceived.remove(accumulate(begin(messages), end(messages), 0, sizeToRemove);
}
};
So basically, we receive data, we insert it into a total array of data received. We stream it, and if we got at least one message, we remove it from the buffer totalDataReceived.
However, I am not sure it is the good way to go. Indeed, this code does not work when a compute a bad crc... (The message is not created, so we don't iterate over it). So each time, I am going to try to read the message with a bad crc...
How can I do this? I can not keep all the data in totalDataReceived because I can receive a lot of messages during the execution life time.
Should I implement my own streambuf?
I found what you want to create is a class which acts like a std::istream. Of course you can choose to create your own class, but I prefer to implement std::streambuf for some reasons.
First, people using your class are accustomed to using it since it acts the same as std::istream if you inherit and implement std::streambuf and std::istream.
Second, you don't need to create extra method or don't need to override operators. They're already ready in std::istream's class level.
What you have to do to implement std::streambuf is to inherit it, override underflow() and setting get pointers using setg().

Qt "tcpserver->write(string)"

i have a very simple question here, How can i send a string with
tcpserver->write(string);
I tried:
tcpserver->write("string")
and it works, but if i want to input a string in there, i get a "no matching function to call to 'QtcpSocket::write(QString)'"
error,
so i tried converting the string to "data" and then send it, but i got a ton of errors...
And my question is: How can i easly send a string thru my tcpserver?
(I should also mention, that i am very new to programming)
You need to convert string to QByteArray, for example:
tcpserver->write(string.toLocal8Bit());
tcpserver->write(string.toUtf8());
Try tcpserver->write((const char *)string.data(), string.length()*sizeof(QChar));
QTcpSocket has 3 overloads for write () function
qint64 write (const char *data);
qint64 write (const char *data, qint64 len);
qint64 write (const QByteArray &data);
So Convert QString to any of them. Just try
tcpserver->write (string.toLatin1 ());

External vs internal declaration of QByteArray

I have currently some problems with the QSerialPort: When I am using the function from an example which looks like
QKeyEvent *e;
emit getData(e->text().toLocal8Bit());
connect(console, SIGNAL(getData(QByteArray)), this, SLOT(writeData(QByteArray)));
void MainWindow::writeData(const QByteArray &data)
{
qDebug() << "Data is to write: " << data;
serial->write(data);
}
then the receiving device can work with the data. But when I change the function writeData() to
void MainWindow::writeData(const QByteArray &data)
{
QString a = "Q";
QByteArray b = a.toLocal8Bit();
serial->write(b);
}
the receiving device can not work with the received data. Where is the difference between those two approaches?
Update: I found out that apparently the data is only usefully transferred if I press Enter after typing the letters. Somehow the '\n' gets lost in the conversion from QString to QByteArray. How can I keep it?
you should add an enter to your Qstring like this
QString a = "Q\x00D";
In the example you have given, there is no "\n" in the QString! It is not getting lost, it is not there in the first place.
If a newline is necessary, then construct the String as QString a = "Q\n".
You can also construct the QByteArray directly from a character array rather than going through a char array -> QString -> QByteArrray conversion sequence, like so:
QByteArray b("Q\n");
EDIT: I realized that your contrived example where you are just sending the letter "Q" is probably a debug attempt, not your real code. In reality, you're getting data in as a QByteArray from some other signal that is emitting a QByteArray. That QByteArray that you are receiving must not include the newline character in it. If you are reading from a file or user input, then that is normal. Most readline-like functions strip off the trailing newline. If it is always necessary to have a newline, you can simply do something like this in your WriteData method:
void MainWindow::writeData(const QByteArray &data)
{
serial->write(data);
serial->write("\n");
}
If sometimes the passed-in QByteArray has a newline at the end and sometimes not, and your receiving device cannot handle redundant newlines, then you'd need to check whether data ends with a newline and only write the "\n" if it does not.
what if you make the QByteArray like this
QByteArray b(&a);

Qt + protobuf, types?

I would like to dip into Google's protocol buffers in Qt development, but I am having trouble figuring out how to incorporate them best.
Ultimately, I want to send with QUdpSocket and QTcpSocket using protocol buffers.
What is the best method for going between a protocol buffer message to sending the data over a socket (QByteArray) and then back again at the other side?
Creating a QByteArray from a protobuf object:
Person person; // a protobuf object
person.set_id(123);
person.set_name("Bob");
person.set_email("bob#example.com");
std::ostringstream out;
person.SerializeToOstream(&out);
QByteArray byteArray(out.str().c_str());
sendSerializedPersonOverQTcpSocket(byteArray);
Reading back a protobuf object from a QByteArray:
QByteArray byteArray = readSerializedPersonFromQTcpSocket();
Person person;
if (!person.ParseFromArray(byteArray, byteArray.size())) {
std::cerr << "Failed to parse person.pb." << std::endl;
}
Instead of:
std::ostringstream out;
person.SerializeToOstream(&out);
QByteArray byteArray(out.str().c_str());
you can also write:
QByteArray byteArray(person.SerializeAsString().c_str());
EDIT: Above two gives the same result, but I'm not sure wether it's correct. This one seems to work better:
QByteArray byteArray(QString::fromStdString(person.SerializeAsString()));
EDIT2: OK, now I know how it works: first two ways are wrong if there are \0 char in serialization - everything after it it's then lost. To correct it one can write:
QByteArray byteArray(person.SerializeAsString().c_str(), person.ByteSize());
Using the code below is really dangerous
std::ostringstream out;
person.SerializeToOstream(&out);
QByteArray byteArray(out.str().c_str());
sendSerializedPersonOverQTcpSocket(byteArray);
You can find a good explanation here In protobuf-c, can optional uint32 variable have value 0
A right way to create a QByteArray from a protobuf message is
QByteArray byteArray;
byteArray.resize(message.ByteSize());
message.SerializeToArray(byteArray.data(), byteArray.size());
#James: You can use ParseFromArray(), for example, as below: (Please note that ParseFromArray() is available only on proto-buf-lite versions of the libs).
void convertQByteArrayToUser(QByteArray& aByteArray)
{
com::your::name_space::User user;
if(!user.ParseFromArray(aByteArray.data(), aByteArray.size()))
{
//could not parse
}
else { //yayyyyy
if(user.has_userid())
{
//...
}
}
}