如何简单地序列化复杂的结构并在 Qt 中通过网络发送它们 [英] How to simply serialize complex structures and send them over a network in Qt
问题描述
我正在创建一个客户端服务器应用程序,现在我正在处理一种最简单的方法来序列化一些类,将其交付给另一端,然后放回我以后可以使用的类中.
I am creating a client server application and now I am dealing with a most simple way to get some of the classes serialized, delivered to other side, and put back into class that I can work with later.
我意识到这并不简单,有些人可能会说在 C 或 C++ 等低级语言中不可能,但实际上通过大量编码是可行的.我想知道是否其他人已经为此创建了一个解决方案,该解决方案可移植且有效,因此我不需要重新发明轮子.
I realize this is not simple, some might say impossible in low level languages like C or C++, but it actually is doable with lot of coding. I am wondering if someone else already didn't create a solution for this, which is portable and works so that I don't need to reinvent a wheel.
目前我的解决方案(可能有点太复杂了):
Currently my solution (maybe a bit too complex):
每个要序列化和反序列化的类都继承自抽象类 Serializable
,它包含 2 个函数:
Every class that is meant to be serialized and deserialized is inherited from abstract class Serializable
which contains 2 functions:
QHash<QString, QVariant> ToHash();
void LoadHash(QHash<QString, QVariant> hash);
第一个函数创建一个包含所有公共和私有变量的 QHash.第二个函数加载这个散列并填充它们所属的值.这样我就可以将需要序列化的每个结构转换为 QHash
并根据需要返回.
First function creates a QHash which contains all public and private variables. Second function loads this hash and fills the values where they belong. So that I can turn every structure I need to serialize to QHash
and back as I need.
现在棘手的部分是通过网络将其传送到另一方.我决定为此创建一个超级简单的 TCP 协议.
Now the tricky part is to deliver this to other side over network. I decided to create a super simple TCP protocol for this.
我可以使用 QDataStream
将这个 QHash
转换成一个字节数组(文档说,虽然我担心可能会有一些隐藏的警告)返回使用相同的类转换为 QHash
.
I can use QDataStream
in order to convert this QHash
into a byte array and (the documentation says that, although I am afraid there might be some hidden caveat) back into QHash
using the same class.
这意味着,我有一种方法可以将结构序列化为 QByteArray
并且我可以将它们转换回来,现在我需要发送和接收它们.
That means, I have a way to serialize structures to QByteArray
's and I can convert them back, now I need to send and receive them.
因为我正在传输二进制数据,所以我决定不对这些数据块"使用某种分隔符,因为我不想弄乱这些分隔符的复杂转义,而是决定创建一个固定大小的标头(我将字节大小存储在名为 HEADER_SIZE
的常量中,目前它是 8 个字节).客户端和服务器的头部大小必须相同.
Because I am transferring binary data I decided not to use some sort of separator for these "data blocks" because I don't want to mess up with complicated escaping of these separators, instead I decided to create a fixed-size header (I store byte size in constant called HEADER_SIZE
, currently it's 8 bytes). The size of header must be same on client and server.
我要做的是获取生成的 QByteArray
(序列化类),获取其大小并从中创建长度正好为 HEADER_SIZE
字节的标头,将实际大小的序列化结构放入其中并将其添加到 QByteArray
中.然后我将这个数组发送到另一端.
What I am going to do is that I take the generated QByteArray
(serialized class), get its size and create header from it which is long exactly HEADER_SIZE
bytes, put a real size of serialized structure in there and prepend it to the QByteArray
. Then I send this array to other side.
另一方面,我正在等待第一个 HEADER_SIZE
字节,然后获取与标头中描述的字节数完全相同的字节,从中形成 QHash
.(我还没有这个代码,现在只是一个理论算法).
On other side I am waiting for first HEADER_SIZE
bytes and then get exactly as many bytes as described in header, forming the QHash
back from it. (I don't have this code yet, it's just a theoretical algorithm right now).
这是正确的做法吗?没有更简单的方法吗?或者已经为我解决了这个问题的东西?是否有一些我应该注意的警告?
只是为了让它更清楚一点,这是我正在尝试做的这个伪图:
Just to make it a little bit more clear, here is this pseudo diagram of what I am trying to do:
推荐答案
您可以而且应该利用 QDataStream
来实现这一点.而不是从 Serializable
派生,只需实现 QDataStream &operator<<(QDataStream &, Type & const)
和 QDataStream &operator>>(QDataStream &, Type &)
对于每个要序列化的 Type
.
You can and should leverage QDataStream
for that. Instead from deriving from Serializable
, simply implement QDataStream & operator<<(QDataStream &, Type & const)
and QDataStream & operator>>(QDataStream &, Type &)
for each Type
you want to serialize.
然后您只需通过 QDataStream
将所有数据转储到 QByteArray
.您将传输数组的大小,然后是其内容.在接收端,您接收大小,然后是内容,在其上设置数据流,然后将数据拉出.它应该是无缝的.
You'd then simply dump all data to a QByteArray
via a QDataStream
. You'd transmit the size of the array, followed by its contents. On the receiving end, you receive the size, then the contents, set a datastream on it, and pull the data out. It should be seamless.
只要正确实现各个流操作符,就会自动为您处理数据块分离.选择 QHash
作为序列化的手段是不必要的限制 - 对于某些类可能是一个不错的选择,但对于其他类则不是.
The data block separation is handled automatically for you as long as the individual streaming operators are implemented correctly. The choice of a QHash
as a means of serialization is unnecessarily limiting - it may be a good choice for some classes, but not for others.
与其序列化为 QHash
,不如序列化为 QDataStream
.但是操作符可以是独立的函数,所以你不需要从任何特殊的接口类派生出来.编译器会提醒您任何缺少的运算符.
Instead of serializing into a QHash
, you should be serializing into a QDataStream
. But the operators for that can be free-standing functions, so you don't need to derive from any special interface class for that. The compiler will remind you of any missing operators.
这是一个非常简单的示例,通过 UDP 工作.
This is a very simple example, working over UDP.
这是一个更大的例子,展示了面向未来的版本控制的细节,并展示了相当复杂数据的序列化结构 - QAbstractItemModel
.
This is a larger example that shows the details of future-proof versioning, and shows serialization of a rather complex data structure - a QAbstractItemModel
.
一般来说,三个对象a,b,c
的序列化可能如下所示:
Generally speaking, the serialization of, say, three objects a,b,c
might look as follows:
static const QDataStream::Version kDSVersion = QDataStream::Qt_5_5;
void Foo::send() {
QByteArray buf;
QDataStream bds(&buf, QIODevice::WriteOnly));
bds.setVersion(kDSVersion);
bds << a << b << c;
QDataStream ds(socket, QIODevice::WriteOnly);
ds.setVersion(kDSVersion);
ds << buf; // buffer size followed by data
}
在接收端:
void Foo::readyReadSlot() {
typedef quint32 QBALength;
if (socket->bytesAvailable() < sizeof(QBALength)) return;
auto buf = socket->peek(sizeof(QBALength));
QDataStream sds(&buf);
// We use a documented implementation detail:
// See http://doc.qt.io/qt-5/datastreamformat.html
// A QByteArray is serialized as a quint32 size followed by raw data.
QBALength size;
sds >> size;
if (size == 0xFFFFFFFF) {
// null QByteArray, discard
socket.read(sizeof(QBALength));
return;
}
if (socket->bytesAvailable() < size) return;
QByteArray buf;
QDataStream bds(&socket);
bds.setVersion(kDSVersion);
bds >> buf;
QDataStream ds(&buf);
ds.setVersion(kDSVersion);
ds >> a >> b >> c;
}
这篇关于如何简单地序列化复杂的结构并在 Qt 中通过网络发送它们的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!