2009-04-15 24 views
4

如果你有下面的类为一个网络数据包的有效载荷:网络数据包有效载荷数据应该在适当的边界上对齐吗?

类有效载荷 { 焦炭field0; int field1; char field2; int field3; };

在通过套接字接收数据时,使用像Payload这样的类会使数据的接收者容易受到对齐问题的影响吗?我认为课程要么需要重新排序,要么添加填充以确保对齐。

要么重新排序:

class Payload 
{ 
    int field1; 
    int field3; 
    char field0; 
    char field2; 
}; 

或添加填充:

class Payload 
{ 
    char field0; 
    char pad[3]; 
    int field1; 
    char field2; 
    char pad[3]; 
    int field3; 
}; 

如果重新排序不因某种原因是有意义的,我想加入填充将是首选,因为它会避免对齐即使它会增加班级的规模也会出现问题。

您对网络数据中的这种对齐问题有什么经验?

+0

这是一个有趣的帖子,关于网络程序员应该知道的东西 - http://stackoverflow.com/questions/366257/everything-ac-developer-should-know-about-network-programming – zooropa 2009-04-16 15:17:13

回答

4

你应该看看Google protocol buffers,或像另一张海报说的Boost :: serialize。

如果您想自己推出,请做对。

如果使用从stdint.h类型(即:uint32_t, int8_t,等),并确保每一个变量有“本机对准”(意思是它的地址是由它的大小均匀整除(int8_t s为在任何地方,uint16_t s为上即使地址为uint32_t s的地址也可以被4除尽),您不必担心对齐或打包

在之前的工作中,我们通过数据总线发送了所有结构(以太网或CANbus或byteflight或串行端口)有一个解析器,用于验证结构中变量的对齐(提醒您是否有人编写了错误的XML),然后生成各种平台和语言的头文件以发送和接收结构。这工作真的很适合我们,我们从来没有不得不担心手写代码做消息解析或打包,并保证所有平台不会有愚蠢的小编码错误。我们的一些数据链路层的带宽有限,所以我们实现了像位域这样的事情,解析器为每个平台生成适当的代码。我们也列举了一些枚举,这非常好(你会感到惊讶的是,人类通过手工来枚举编码位域是多么容易)。

除非您需要担心它在带有C的8051和HC11上运行,或者在带宽受限的数据链路层上运行,您不会想出比协议缓冲区更好的东西,您只需花费一个很多时候都试图与他们保持一致。

8

正确,一味地忽略对齐可能会导致问题。即使在相同的操作系统上,如果使用不同的编译器或不同的编译器版本编译了2个组件。

最好......
1)通过某种序列化过程传递数据。
2)或单独通过每个图元,同时还注重字节顺序== Endianness

一个良好的开端是Boost Serialization

+0

+1:正确的序列化节拍与此混乱。 – 2009-04-15 20:02:09

+0

+1序列化是正确的答案。 – RBerteig 2009-04-16 06:25:44

2

我的经验是,下面的方法将被优先(按优先顺序排列):

  1. 使用像Tibco的,CORBA,DCOM一个高层次的框架或任何会为您管理所有这些问题。

  2. 在连接的双方都知道打包,字节顺序和其他问题编写自己的库。

  3. 仅使用字符串数据进行通信。

试图发送未经任何调解的原始二进制数据几乎肯定会导致很多问题。

1

如果您想要任何类型的可移植性,您实际上不能使用此类或结构。在你的例子中,整数可能是32位或64位,具体取决于你的系统。你最有可能使用一个小型机器,但是较老的Apple机器是大型机器。编译器可以随意放置,因为它也是。

通常,您需要一种方法,在确保您使用n2hll,n2hl或n2hs获取字节顺序后,每次将每个字段写入缓冲区一个字节。

4

我们在内存中使用直接叠加在二进制数据包上的打包结构,我正在决定做这件事的那一天。我们已经得到了这个工作的唯一方法是:

  1. 仔细定义位宽根据编译环境的具体类型(typedef unsigned int uint32_t
  2. 中插入相应的特定于编译器的编译指定的紧密堆积结构成员
  3. 要求一切都在一个字节顺序(使用网络或大端排序)
  4. 精心编写服务器和客户端代码

如果您刚刚开始,我会建议您跳过试图用结构表示线路上的内容。只需分别序列化每个基元。如果您选择不使用像Boost Serialize这样的现有库或者像TibCo这样的中间件,那么可以通过在隐藏序列化方法细节的二进制缓冲区周围编写一个抽象来节省您很多头痛的问题。瞄准像一个接口:

class ByteBuffer { 
public: 
    ByteBuffer(uint8_t *bytes, size_t numBytes) { 
     buffer_.assign(&bytes[0], &bytes[numBytes]); 
    } 
    void encode8Bits(uint8_t n); 
    void encode16Bits(uint16_t n); 
    //... 
    void overwrite8BitsAt(unsigned offset, uint8_t n); 
    void overwrite16BitsAt(unsigned offset, uint16_t n); 
    //... 
    void encodeString(std::string const& s); 
    void encodeString(std::wstring const& s); 

    uint8_t decode8BitsFrom(unsigned offset) const; 
    uint16_t decode16BitsFrom(unsigned offset) const; 
    //... 
private: 
    std::vector<uint8_t> buffer_; 
}; 

的每个类必须序列化到一个ByteBuffer或者从ByteBuffer和反序列化偏移的方法。这是我绝对希望我能够及时回到正确的那些东西之一。我无法计算我花费时间调试由于忘记交换字节或未包装struct而导致的问题的次数。

要避免的另一个陷阱是使用union来表示字节或memcpy ing到unsigned char缓冲区以提取字节。如果你总是在电线上使用大端,然后就可以用简单的代码来写字节的缓冲区,而不是担心htonl东西:

void ByteBuffer::encode8Bits(uint8_t n) { 
    buffer_.push_back(n); 
} 
void ByteBuffer::encode16Bits(uint16_t n) { 
    encode8Bits(uint8_t((n & 0xff00) >> 8)); 
    encode8Bits(uint8_t((n & 0x00ff) )); 
} 
void ByteBuffer::encode32Bits(uint32_t n) { 
    encode16Bits(uint16_t((n & 0xffff0000) >> 16)); 
    encode16Bits(uint16_t((n & 0x0000ffff)  )); 
} 
void ByteBuffer::encode64Bits(uint64_t n) { 
    encode32Bits(uint32_t((n & 0xffffffff00000000) >> 32)); 
    encode32Bits(uint32_t((n & 0x00000000ffffffff)  )); 
} 

这很好地保留了平台无关的,因为数字表示始终逻辑上大端。这段代码也非常适合使用基于原始类型大小的模板(想想encode<sizeof(val)>((unsigned char const*)&val))......不是很漂亮,但是非常容易编写和维护。

1

如果在结构中没有自然对齐,编译器通常会插入填充以便对齐正确。但是,如果您使用编译指示来“包装”结构(删除填充),则可能会产生非常有害的副作用。在PowerPC上,非对齐的浮点数会产生一个异常。如果您在处理该异常的嵌入式系统上工作,您将得到重置。如果有是一个处理该中断的例程,它可以减慢你的代码的速度,因为它将使用软件例程来解决错位问题,这会使你的性能悄无声息地受损。

相关问题