代码之家  ›  专栏  ›  技术社区  ›  Paul Kelly

网络包有效载荷数据是否应在适当的边界上对齐?

  •  4
  • Paul Kelly  · 技术社区  · 15 年前

    如果将以下类作为网络数据包负载:

    类有效载荷 { 焦炭场0; int字段1; 焦炭场2; 字段3; };

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

    重新排序:

    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; 
    };
    

    如果由于某种原因重新排序没有意义,我认为最好添加填充,因为这样可以避免对齐问题,即使它会增加类的大小。

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

    6 回复  |  直到 7 年前
        1
  •  4
  •   Joaommp    7 年前

    你应该调查一下 Google protocol buffers 或者像另一张海报上说的那样序列化。

    如果你想自己滚,请做对。

    如果使用stdint.h中的类型(即: uint32_t, int8_t, 等等),并确保每个变量都有“本机对齐”(意味着它的地址可以被它的大小平均分割( int8_t S在任何地方, uint16_t S在偶数地址上, uint32_t S位于可被4整除的地址上),您不必担心对齐或打包。

    在上一个工作中,我们通过XML定义的数据总线(以太网、CANBUS、ByteLight或串行端口)发送了所有结构。有一个解析器可以验证结构中变量的对齐情况(如果有人写了错误的XML,就会向您发出警报),然后为各种平台和语言生成头文件来发送和接收结构。这起作用了 真的? 对我们来说,我们 从未 不得不担心手工编写代码来进行消息解析或打包,并且可以保证所有平台都不会有愚蠢的小编码错误。我们的一些数据链路层的带宽非常有限,因此我们实现了比特字段等功能,解析器为每个平台生成适当的代码。我们还进行了枚举,这非常好(您可能会惊讶,人工对枚举上的位字段进行编码是多么容易)。

    除非您需要担心它在8051s和HC11s上运行,使用C,或者在带宽非常受限的数据链路层上运行,否则您不会想到比协议缓冲区更好的东西,您只需要花费大量时间来尝试与它们并驾齐驱。

        2
  •  8
  •   Brian R. Bondy    15 年前

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

    最好…
    1)通过某种序列化过程传递数据。
    2)或者单独传递每个原语,同时仍然注意字节顺序== Endianness

    一个好的起点是 Boost Serialization .

        3
  •  4
  •   D.Shawley    15 年前

    今天,我们使用直接覆盖在内存中二进制包上的打包结构,我很后悔我决定这么做的那一天。唯一的方法就是:

    1. 根据编译环境仔细定义特定于位宽度的类型( typedef unsigned int uint32_t )
    2. 插入适当的编译器特定的pragma以指定结构成员的紧密封装
    3. 要求所有内容都按一个字节顺序(使用网络或big-endian顺序)
    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 或者从 字节缓冲区 和偏移。这是我绝对希望能回到过去并改正的事情之一。我无法计算我花时间调试由忘记交换字节或不打包导致的问题的次数。 struct .

    另一个要避免的陷阱是使用 union 表示字节或 memcpy 转到无符号字符缓冲区以提取字节。如果您总是在线路上使用big-endian,那么您可以使用简单的代码将字节写入缓冲区,而不必担心 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) )不是很漂亮,但是非常,非常容易写和维护。

        4
  •  2
  •   David Rodríguez - dribeas    15 年前

    我的经验是,优先采用以下方法(按优先顺序):

    1. 使用一个高级框架,如TIBCO、CORBA、DCOM或其他可以为您管理所有这些问题的工具。

    2. 在连接的两侧编写您自己的库,这些库知道打包、字节顺序和其他问题。

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

    尝试在没有任何中介的情况下发送原始二进制数据几乎肯定会导致许多问题。

        5
  •  1
  •   Andrew Johnson    15 年前

    如果您想要任何类型的可移植性,实际上就不能使用类或结构来实现这一点。在您的示例中,根据您的系统,ints可以是32位或64位。你很可能使用的是一个小的endian机器,但是旧的苹果Mac是big endian。编译器也可以随意填充。

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

        6
  •  1
  •   KeyserSoze    15 年前

    如果在结构中没有自然对齐,编译器通常会插入填充,以便对齐正确。但是,如果使用pragma对结构进行“打包”(移除填充),可能会产生非常有害的副作用。在PowerPC上,不对齐的浮动会生成异常。如果你在一个不处理这个异常的嵌入式系统上工作,你会得到一个重置。如果有 一个处理中断的程序,它可以 彻底地 降低代码的速度,因为它将使用一个软件例程来解决未对准问题,这将悄悄地削弱您的性能。