通过网络发送结构

时间:2015-11-04 12:09:43

标签: c++ struct network-programming sizeof

在我们公司,通过网络发送C / C ++结构是很常见的。任何结构都有一个或多个uint32_t字段:

typedef struct {
    uint32_t data1;
    uint32_t data2;
} Data;

和fields(data1,data2,...)封装了我们需要发送的实际数据。我们的数据大多数是一位或几位长,因此为了节省空间/带宽,它在特定位位置的那些字段内对齐。

要访问我们的真实数据,我们编写(在结构之外!)带有位移和位屏蔽的'getters'和'setters'宏:

#define READY_MASK 0x01      // indicates that READY is a single bit value
#define READY_OFFSET 3       // indicates position of READY inside 32-bit field
#define IS_READY(x) { ... }  // returns READY value from x field
#define SET_READY(x,r) { ... }  // sets READY value to x field

现在,我想通过将getter和setter直接添加到结构中来修改,简化并使此过程更安全,例如:

typedef struct {
    uint32_t data1;

    #define READY_MASK 0x01
    #define READY_OFFSET 3
    inline void set_ready(uint32_t r) { /*...*/ }
    inline uint32_t is_ready() { /*...*/ }
    // lots of other getters and setters
} Data1;

就我的实验是正确的,我注意到这种修改不会影响结构sizeof(Data)==sizeof(Data1)的大小,我可以通过网络发送此结构并在另一个上接收和解码端。

我的问题是:这次修改有什么不对吗?是否有任何风险或任何我应该注意的事情?

2 个答案:

答案 0 :(得分:2)

你所做的修改并没有造成任何伤害,但它也没有任何好处,因为结构成员无论如何都是公共的。 Data和Data1之所以大小相同,是因为你添加的是内联函数,它们不会占用对象中的空间,而是用实际的函数代码替换任何函数调用。

现在,如果您通过网络发送结构和二进制数据,则必须考虑以下两个规则:

  • 按照惯例,使用Big Endian网络字节序发送整数值,而不是x86的Little Endian。如果您确定发送和接收数据的所有计算机都是x86,则这不是问题。但是,如果任何计算机具有不同的字节序,例如Big Endian或No Endian(或Middle Endian),则会出现问题。例如,您可以拥有ARM处理器,SPARC等。在C中,您有以下宏:

    ntohs,htons,ntohl,htonl

  • 您还必须考虑不同的内存对齐方式。结构的内存对齐取决于体系结构,编译器和编译模式。编译器可能会添加填充(他们不能在C中重新排序成员,但请阅读:Can a C++ compiler re-order elements in a struct)。此外,像long这样的类型在32位和64位架构中具有不同的大小。即使你非常确定你的两个无符号int成员都不会遇到问题,你也不应该从结构到数据缓冲区的内存复制,这些内存将在消息中发送,也不会从内存中复制原始数据。消息到结构。基本上你不应该这样做:

    char buffer [BUF_SIZE];

    Data1 myData;

    memcpy(buffer,myData,sizeof(Data));

https://en.wikipedia.org/wiki/Data_structure_alignment

您应该逐个将成员移动到缓冲区。从消息中收到的原始数据填充结构时也是如此。

我还建议不要使用位字段,请阅读Why bit endianness is an issue in bitfields?。但要非常清楚,在你的代码片段中你没有使用位域。使用位域与使用整数中的位来表示某些特定状态不同。在这种情况下,您做得很好,应该只考虑具有不同字节序的体系结构中的整数表示(如果这是一种可能的情况)。

答案 1 :(得分:1)

原则上,如果不添加虚函数,则不存在vptr,结构大小应与没有成员函数的情况相同。但它不再是POD结构,所以你可能很容易徘徊在UB的未知行为中。由于C ++(尚未)具有标准化的ABI,因此采用C风格的POD结构通常是很好的。通过网络进行序列化。同样适用于为C ++模块/子系统制作精简,可移植的API,坚持使用C ABI。

另外,如果您考虑那些(如某些评论者的建议),我会建议您远离位域,手动位掩码通常更容易分析和便携。比特场可以例如给你带有字节序的问题,这至少部分是因为结构的第一个成员'always'最终在struct start的较低内存地址处结束,而不管endianness。位域如何工作也可能取决于传输介质,例如BSD网络流套接字是字节流,但许多HW接口复制整个32位字大小的块而不是字节。