如何简单地序列化复杂的结构并在 Qt 中通过网络发送它们 [英] How to simply serialize complex structures and send them over a network in Qt

查看:28
本文介绍了如何简单地序列化复杂的结构并在 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆