3

我有一个多播某些打包 POD 结构的应用程序和一个在其他二进制文件中运行的侦听器服务。侦听器服务知道结构是什么样的,因此当它接收到它们时,它会将其转换回用 a 敲击的结构reinterpret_cast并执行回调。

下线的问题是,如果二进制文件被发布并且需要将新信息添加到结构中,则必须重新构建这些二进制文件,否则它们将reinterpret_cast滥用和滥用信息。在生产环境中,这可能是一个问题,因为它一直没有那种灵活性。

有人告诉我的一件事是,解决方法是引入新样式的消息,并将它们都发送出去……随着时间的推移,应用程序最终将切换到新类型的二进制消息,直到可以停止发送旧的。我想知道是否有更好的选择。

例如,如果一个人约定只在打包结构的末尾添加新字段,那么旧的监听二进制文件可能仍然能够访问这些新字段,如果他们愿意,使用旧信息构建的那些可能仍然能够访问顶部。例如,如果发送者正在多播:

struct foo {
    int  a;
    char b[2];
} __attribute__ ((packed));

然后在接收端构建了几个二进制文件,这些二进制文件通过网络获取const char* msg消息并执行以下操作:

foo* fooPtr = reinterpret_cast<foo*>(msg); 
registeredGuy->callback(fooPtr);

现在,如果我们决定在发送方推出额外的信息,如果我们像这样将它固定到底部,老听众可能会没事:

struct foo {
    int  a;
    char b[2];
    char newStuff[17];
    int  k;
} __attribute ((packed));

老接收者应该仍然能够成功投射和访问他们的旧信息,而新人可以访问新内容。这是真的?是否有更好的解决方案不会影响速度(性能非常关键)

4

2 回答 2

1

发送本质上相同消息的两个版本是一个坏主意。特别是对于性能关键的系统。您最终花费至少两倍的时间进行实际发送。您最终还会广播两倍以上的信息,因此您的网络可能会饱和。

只要消息有效负载本身就存在版本控制消息有效负载的问题。我的想法是,解决问题的最好方法是完全避免它。

关键是了解客户端将如何接收和处理入站数据。通常,客户端将侦听线路上的 UDP 帧,将其吸入,并将该帧作为消息处理。理想情况下,您的 UDP 帧小于您架构的 MTU(例如 1500 字节),因此消息不会在传输过程中被截断。他们可能会乱序到达,但这是一个完全不同的问题。

客户知道 UDP 帧有多大,因为他们把它从网络上拉下来了。他们也知道他们将处理的消息有多大,因为它只是sizeof(MessageType). 他们唯一不知道的是帧大小和有效载荷大小之间的差异。您可以通过在每条消息中包含一个固定大小的标头来告诉他们。

标头看起来像这样:

struct MsgHeader
{
  size_t  msg_size_;
  int  msg_type_;
  char payload_[0];
};

实际的消息要么覆盖它,要么紧随其后(在 中&payload_[0])。

客户端现在读取 UDP 帧,从标头中获取帧的大小,并将那么多总字节作为单个消息提取。从负载指针开始,客户端将入站数据转换为相关消息类型。如果单个消息中的数据多于客户端理解的消息类型,客户端会忽略它并将其丢弃在地板上。

当您对消息进行更改时,您需要通过不移动任何现有字段的位置来确保向后二进制兼容性。在末尾添加新字段,相应地增加标题中的帧大小,Bob 就是你的叔叔。

于 2012-11-16T20:05:05.093 回答
0

通常,您不会更改原始结构以添加其他字段,而是定义一个新结构,该结构在开始时具有与原始结构相同的字段。为了使旧客户端能够安全地处理新版本结构的二进制形式,需要将实例的大小作为字段包含在第一个版本中。

通过在每个实例中包含大小,然后 foo 可以在未来扩展到 fooex 并且旧客户端仍然可以安全地处理由 foo 和 fooex 混合组成的一系列元素,因为旧客户端可以检查每个实例的大小而不是假设它们是 foo 的大小。

例如:

struct foo {
 size_t size;
 int  a;
 char b[2];
} __attribute__ ((packed));

struct fooex {
 size_t size;
 int  a;
 char b[2];
 char newStuff[17];
 int  k;
} __attribute ((packed));
于 2012-11-16T19:46:59.713 回答