我有一个应用程序,它可以组播某些打包的POD结构和一个在其他二进制文件中运行的监听器服务。侦听器服务知道结构的样子,因此当它接收到它时,它会将其强制转换为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));
旧接收器应该能够仍然成功地投射和访问他们的旧信息,而新人可以访问新的东西。这是真的?是否有更好的解决方案,不会导致速度受到影响(性能非常关键)
答案 0 :(得分:1)
发送基本上相同消息的两个版本是一个坏主意。特别是对于性能关键系统。您最终花费的时间至少是实际发送的两倍。您最终播放的信息数量也会增加两倍以上,因此您可能会使网络饱和。
只要mesasge有效负载本身存在版本化消息有效负载的问题。我的思维方式是解决问题的最佳方法是完全避免它。
关键是了解客户端如何接收和处理入站数据。通常,客户端将在线路上侦听UDP帧,将其吸入,并将该帧作为消息进行处理。理想情况下,您的UDP帧小于您的体系结构的MTU(比如1500字节),因此消息不会在传输过程中被切断。它们可能无序到达,但这是一个完全不同的问题。
客户端知道UDP帧有多大,因为他们将其从线路中拉出来。他们也知道他们将处理的消息有多大,因为它只是sizeof(MessageType)
。他们唯一不知道的是帧的大小和有效载荷的大小之间的差异。您可以通过在每条消息中包含固定大小的标头来告诉他们。
标题看起来像这样:
struct MsgHeader
{
size_t msg_size_;
int msg_type_;
char payload_[0];
};
实际消息要覆盖此消息,要么紧接在消息之后(在&payload_[0]
中)。
客户端现在读取UDP帧,从头部获取帧的大小,并将那么多的总字节拉为单个mesasge。从有效负载指针开始,客户端将入站数据转换为相关消息类型。如果单个消息中的数据多于客户端理解的消息类型中的数据,则客户端会忽略它并将其丢弃在地板上。
当您对mesasges进行更改时,您需要通过不移动任何现有字段的位置来确保向后二进制兼容性。最后添加新字段,相应地增加标题中的帧大小,Bob是你的叔叔。
答案 1 :(得分: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));