如何在Qt中简单地序列化复杂结构并通过网络发送它们

时间:2015-10-19 14:53:54

标签: c++ qt networking serialization

我正在创建一个客户端服务器应用程序,现在我正在处理一种最简单的方法,将一些类序列化,交付给另一方,然后放回到我可以使用的类中。

我意识到这并不简单,有些人可能会说在C或C ++这样的低级语言中是不可能的,但它实际上可以用很多编码来实现。我想知道是否其他人已经没有为此创建一个解决方案,这个解决方案是便携式的并且可以工作以便我不需要重新发明轮子。

目前我的解决方案(可能有点过于复杂):

每个要序列化和反序列化的类都是从包含2个函数的抽象类Serializable继承而来的:

QHash<QString, QVariant> ToHash();
void LoadHash(QHash<QString, QVariant> hash);

第一个函数创建一个包含所有公共和私有变量的QHash。第二个函数加载此哈希并填充它们所属的值。因此,我可以将我需要序列化的所有结构转换为QHash,并根据需要将其返回。

现在棘手的部分是通过网络将此交付给另一方。我决定为此创建一个超级简单的TCP协议。

我可以使用QDataStream将此QHash转换为字节数组,并且(文档说,虽然我担心可能会有一些隐藏的警告)回到QHash使用同一个班级。

这意味着,我有办法将结构序列化为QByteArray并且我可以将它们转换回来,现在我需要发送和接收它们。

因为我正在传输二进制数据,所以我决定不对这些&#34;数据块&#34;使用某种分隔符。因为我不想搞砸这些分隔符的复杂转义,而是我决定创建一个固定大小的标题(我将字节大小存储在常量中称为HEADER_SIZE,目前它是8个字节)。客户端和服务器上的标头大小必须相同。

我要做的是我获取生成的QByteArray(序列化类),获取它的大小并从中创建标题,其长度恰好是HEADER_SIZE个字节,放入一个实际大小的序列化在那里的结构并将其添加到QByteArray。然后我把这个数组发送到另一边。

另一方面,我正在等待第一个HEADER_SIZE字节,然后获得与标头中描述的字节数完全相同的字节数,从而形成QHash。 (我还没有这个代码,它现在只是一个理论算法)。

这是一种正确的方法吗?难道还有更简单的方法吗?还是已经为我处理过这个问题的东西?我应该注意一些警告吗?

为了让它更清楚一点,这是我想要做的伪图:

enter image description here

3 个答案:

答案 0 :(得分:3)

您可以而且应该利用QDataStream。而不是从Serializable派生,只需为要序列化的每个QDataStream & operator<<(QDataStream &, Type & const)实施QDataStream & operator>>(QDataStream &, Type &)Type

然后,您只需通过QByteArray将所有数据转储到QDataStream。您传输数组的大小,然后是其内容。在接收端,您将收到大小,然后是内容,在其上设置数据流,并将数据拉出。它应该是无缝的。

只要正确实施各个流媒体运营商,就会自动处理数据块分离。选择QHash作为序列化的手段是不必要的限制 - 它可能是某些类的好选择,但对其他类则不是。

您应该序列化为QHash,而不是序列化为QDataStream。但是,操作员可以是独立的功能,因此您不需要从任何特殊的接口类派生。编译器会提醒您任何缺少的运算符。

This is a very simple example,通过UDP工作。

This is a larger example显示了面向未来的版本控制的详细信息,并显示了相当复杂的数据结构的序列化 - QAbstractItemModel

一般来说,三个对象a,b,c的序列化可能如下所示:

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;
}

答案 1 :(得分:0)

是的,有人已经发明了这个。

建议您查看谷歌协议缓冲区。它不仅可以在c ++实现和处理器体系结构之间移植,而且还可以在不同语言之间移植。

https://developers.google.com/protocol-buffers/

答案 2 :(得分:0)

您可以使用JSON协议

发送这样的结构:

{"employees":[
{"firstName":"John", "lastName":"Doe"},
{"firstName":"Anna", "lastName":"Smith"},
{"firstName":"Peter", "lastName":"Jones"}]}

你可以根据需要制作它。

Qt有QJsonObject库,可以很容易地使用它。