【问题标题】:Deserialization of structure data sent by Big Endian system in Little Endian system大端系统发送的结构数据在小端系统中的反序列化
【发布时间】:2011-02-10 14:56:35
【问题描述】:

我有一个 C 程序,它通过套接字以 UDP 数据包的形式从大型机接收数据。 C 程序的主机正在从 Unix(大端)更改为 Linux(小端),程序不再工作。我目前没有更改大型机客户端程序的选项。

程序执行recvfrom 并将数据接收到一个字符数组中。以前,我们能够简单地将这个缓冲区转换为与从 MF 传递的内容相匹配的结构,并且一切正常。现在,由于不同的字节对齐方式,到结构的映射失败。这是结构和一些代码。

struct CCClntPkt
{
    unsigned short packet_type;
    unsigned short reply_socket;
    unsigned long  msg_ID;
    unsigned short msg_length;
    unsigned char  client_string[250];
};

之前用于将接收到的数据缓冲区转换为该结构的代码如下所示:

char BCpacket_in[MAX_PACKET];
struct CCClntPkt *pClntPkt;

<snip>

rcv_cnt = recvfrom(BCServerSocket, BCpacket_in,
                sizeof(BCpacket_in),0x0,(struct sockaddr *)&from,
                &fromlen);

if (rcv_cnt > 0)
{
    pClntPkt = (struct CCClntPkt *) &BCpacket_in;
}

我能够通过使用ntohs 获得 packet_type 和 reply_socket 的正确值,但是字符字段 client_string 被破坏了。我还尝试在结构之前放置pragma pack(1),在结构之后放置pragma pack(0),但似乎仍然存在对齐问题。

我还尝试了从BCpacket_in 移位值,并且能够获得 packet_type 和 reply_socket 的正确值,但无法弄清楚如何提取 ulong msg_ID。代码是:

packet_type = BCpacket_in[0] << 8;
packet_type |= BCpacket_in[1];

reply_to_socket = BCpacket_in[2] << 8;
reply_to_socket |= BCpacket_in[3];

/*
msg_ID = BCpacket_in[4] << 24;
msg_ID |= BCpacket_in[5] << 16;
msg_ID |= BCpacket_in[6] << 8;
msg_ID |= BCpacket_in[7];
*/

在这一点上我很困惑,所以任何帮助表示赞赏。我不是这个程序的原作者,我的 C 知识非常有限。不过,我不介意做这项工作,所以我也希望能提供任何相关的参考资料。谢谢!

【问题讨论】:

  • 假设它是 32 位机器,您注释掉的代码应该可以解决问题。 char

标签: c sockets endianness


【解决方案1】:

您必须手动将收到的数据包 (BCpacket_in) 解析为 struct CCClntPkt 数据包,这是唯一可移植的方法。使用ntohl(网络到主机long)系列函数可以正确处理字节顺序转换;请参阅手册页 byteorder(3)endian(3)

这些函数假定所有数据包都以大端方式通过网络发送,因为这是互联网标准。

【讨论】:

    【解决方案2】:

    从您的大端主机到新的小端主机,各种类型的大小可能不同。

    如果你在你的两台主机上编译这个程序,它会显示struct的大小和布局:

    #include <stddef.h>
    #include <stdio.h>
    
    struct CCClntPkt
    {
        unsigned short packet_type;
        unsigned short reply_socket;
        unsigned long  msg_ID;
        unsigned short msg_length;
        unsigned char  client_string[250];
    };
    
    int main()
    {
        printf("sizeof(unsigned short) = %u\n", (unsigned)sizeof(unsigned short));
        printf("sizeof(unsigned long) = %u\n", (unsigned)sizeof(unsigned long));
    
        printf("offsetof(struct CCClntPkt, reply_socket) = %u\n", (unsigned)offsetof(struct CCClntPkt, reply_socket));
        printf("offsetof(struct CCClntPkt, msg_ID) = %u\n", (unsigned)offsetof(struct CCClntPkt, msg_ID));
        printf("offsetof(struct CCClntPkt, msg_length) = %u\n", (unsigned)offsetof(struct CCClntPkt, msg_length));
        printf("offsetof(struct CCClntPkt, client_string) = %u\n", (unsigned)offsetof(struct CCClntPkt, client_string));
    
        return 0;
    }
    

    特别是,long 在新主机上的时间很可能比旧主机上的时间长。这可能是使用来自 &lt;stdint.h&gt; 的 C99 精确宽度类型的好地方 - 如果在您的原始主机上,short 是 16 位类型,long 是 32 位类型,请将它们替换为 @分别为987654327@和uint32_t

    然后您可以使用ntohs()ntohl() 执行字节序更正。

    【讨论】:

      【解决方案3】:
      msg_ID = BCpacket_in[4] << 24;
      msg_ID |= BCpacket_in[5] << 16;
      msg_ID |= BCpacket_in[6] << 8;
      msg_ID |= BCpacket_in[7];
      

      这对我来说似乎是正确的。

      尝试使用 unsigned char 作为缓冲区来保护自己免受签名问题的影响。

      顺便说一句,是大端的msg_id,你确定偏移量吗:正如你所说的“打包”在客户端不起作用,所以可以得出结论,结构是使用打包规则发送到线路的大型机。

      【讨论】:

      • ntohl 有什么问题?它在 x86-linux 上应该比 bitshift 机制更快,因为它将使用内置的汇编器操作进行字节交换操作
      • ntohl() 需要一个变量,它不能与指针一起使用,因此为了使用 ntohl(),您必须创建一个指向 long 的指针,如果地址不是,它可能会中断正确对齐,否则您必须将字节复制到一个长变量中,而不是调用 ntohl(),它正在执行 2 倍字节复制。
      • @bdonlan 32 位逻辑移位在效率方面很难被击败。除了数组索引之外,它还应该被转换为单个 CPU 指令。我怀疑您不会注意到这两种方法之间的速度差异。
      • @Lundin,你正在做三个轮班和三个 OR。 x86 上ntohl 的 Linux 实现执行三个逻辑循环,利用 32 位寄存器的 16 位一半。就是这样。如果传入正确的 CPU 标志,它只使用内置的BSWAP 汇编器操作。所以是的,它确实在 x86 和 x86-64 上击败了 32 位逻辑移位。
      • @bdonlan 好的,我明白了。尽管一个聪明的优化器也许应该能够做到这一点。不过,在这种情况下,我怀疑一些指令或多或少会很重要。
      【解决方案4】:

      这是我通常对从网络发送/接收的打包结构执行的操作:

      #define PACKED __attribute__((__packed__))
      struct PACKED message { ... };
      

      这是特定于 GCC 的,请参阅 here。然后你必须弄清楚long 的大小。它在 32 位和 64 位平台上有所不同。您可能想考虑改用 stdint.h 类型。另请查看信息__builtin_bswap32() and __builtin_bswap64() GCC intrinsics

      【讨论】:

        猜你喜欢
        • 2012-03-03
        • 2021-02-07
        • 1970-01-01
        • 2011-05-10
        • 2011-02-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-20
        相关资源
        最近更新 更多