0

考虑下面的 c++11 类,它表示应该可以从字节数组构造的 IPv4 标头结构,而不管字节顺序如何。

#include <arpa/inet.h>
#include <netinet/in.h>

namespace Net {
  using addr_t = ::in_addr_t;
  #pragma pack(push, 1)
  struct ip_header_t {
    uint8_t  ver_ihl;
    uint8_t  tos;
    uint16_t total_length;
    uint16_t id;
    uint16_t flags_fo;
    uint8_t  ttl;
    uint8_t  protocol;
    uint16_t checksum;
    addr_t   src_addr;
    addr_t   dst_addr;

    ip_header_t( const uint8_t* bytes, const bool ntoh = false ) {
      auto o = (ip_header_t&)*bytes;
      ver_ihl      = o.ver_ihl;
      tos          = o.tos;
      ttl          = o.ttl;
      protocol     = o.protocol;
      total_length = ntoh? ntohs(o.total_length) : o.total_length;
      id           = ntoh? ntohs(o.id) : o.id;
      flags_fo     = ntoh? ntohs(o.flags_fo) : o.flags_fo;
      checksum     = ntoh? ntohs(o.checksum) : o.checksum;
      src_addr     = ntoh? ntohl(o.src_addr) : o.src_addr;
      dst_addr     = ntoh? ntohl(o.dst_addr) : o.dst_addr;
    };
  };
  #pragma pack(pop)
}

我担心接受字节数组可能不是最安全或语义上最正确的方法。将数组转换为结构本身似乎是一种非常 C-ish 的方法,缺乏类型安全(更不用说边界检查)。要求调用者担心这一点并要求对实例进行 const 引用会更好吗?

4

3 回答 3

0

一种表示原始二进制数据的类型,具有某种假定的布局:

template<typename T, size_t order>
struct serial_tag {};

介绍一些代表磁盘上预期数据类型和布局的名称:

typedef serial_tag<uint8_t , 0> ver_ihl_ser;
typedef serial_tag<uint8_t , 1> tos_ser;
typedef serial_tag<uint16_t, 2> total_length_ser;
    ...
typedef serial_tag<addr_t  , 9> dst_addr_ser;

一组serial_tags,然后可以由其他代码操作:

template<typename... tags>
struct serial_pack {};

编写采用 serial_pack 的代码,并确保每个序数都在使用中,没有间隙。

编写采用反序列化迭代器和 serial_tag 的代码,并在从 serial_tag 生成数据的同时推进反序列化迭代器。这应该处理字节序

目标是以适合元编程的方式描述数据的原始数据布局,然后使用此布局信息将数据加载到 C++ 结构中。

这是一个流式读取操作,其中反序列化迭代器(或范围)知道它是否有大小限制,并且知道您是否按顺序正确读取元素(至少在调试中)。

我不知道这是否值得,但它确实解决了您的担忧。

这种方法的一个缺点是它违反了 DRY,因为内存中的布局理论上可以用作描述序列化后的原始字节布局的一种方式。相反,我们必须维护一组完全不同的数据来表示它。作为一个优点,这意味着我们的 in-C++ 布局不必完全复制二进制布局。

于 2013-04-03T18:46:57.740 回答
0

在我看来,最好的解决方案是提供一个可以处理字节顺序转换并依赖调用者进行转换的复制构造函数。

像这样:

/* copy constructor: */
ip_header_t( const ip_header_t& src, const bool ntoh = false )
  : ver_ihl(src.ver_ihl),
    tos(src.tos),
    ttl(src.ttl),
    protocol(src.protocol) {
  total_length = ntoh? ntohs(src.total_length) : src.total_length;
  id           = ntoh? ntohs(src.id)           : src.id;
  flags_fo     = ntoh? ntohs(src.flags_fo)     : src.flags_fo;
  checksum     = ntoh? ntohs(src.checksum)     : src.checksum;
  src_addr     = ntoh? ntohl(src.src_addr)     : src.src_addr;
  dst_addr     = ntoh? ntohl(src.dst_addr)     : src.dst_addr;
};

/* client code using byte array in network-order */
auto ip_header = Net::ip_header_t((Net::ip_header_t&)*(byte_array), true);

我也不是 100% 确定我最喜欢这个解决方案。考虑到字节顺序交换与对象的构造并不严​​格相关,创建一个执行此操作的非成员函数可能会更好。关注字段对齐和排序也可能不是该类的正当责任。

于 2013-04-08T21:23:47.070 回答
0

将字节数组转换为此类绝对不是正确的做法-正如您所提到的,不同系统上的字节顺序可能不同(这就是ntohs构造函数中存在的原因)。

你把你的类放在哪里完全取决于你的实体的角色和职责。没有看到设计,就无法判断。

于 2013-04-03T18:36:09.077 回答