反转C结构的字节顺序

我在C中有一个看起来像这样的结构:

typedef u_int8_t NN; typedef u_int8_t X; typedef int16_t S; typedef u_int16_t U; typedef char C; typedef struct{ X test; NN test2[2]; C test3[4]; U test4; } Test; 

我已经将字段的结构和写入值声明如下:

 Test t; int t_buflen = sizeof(t); memset( &t, 0, t_buflen); t.test = 0xde; t.test2[0]=0xad; t.test2[1]=0x00; t.test3[0]=0xbe; t.test3[1]=0xef; t.test3[2]=0x00; t.test3[3]=0xde; t.test4=0xdeca; 

我通过UDP将此结构发送到服务器。 目前我在本地测试时工作正常,但是我现在需要将这个结构从我的little-endian机器发送到big-endian机器。 我真的不确定怎么做。

我已经研究过使用htons但是我不确定这是否适用于这种情况,因为它似乎只是为16或32位的unsigned ints定义,如果我理解正确的话。

我认为这里可能存在两个问题,具体取决于您如何通过TCP发送此数据。

问题1:字节顺序

至于,你已经说过字节序是一个问题。 当你提到使用htonsntohs作为短裤时,你是对的。 你也可以找到htonl ,它的对立面也很有用。

Endianness与内存中多字节数据类型的字节排序有关。 因此,对于单字节宽度的数据类型,您不必担心。 在你的情况下是2字节数据,我想你在质疑。

要使用这些function,您需要执行以下操作…

 Sender: ------- t.test = 0xde; // Does not need to be swapped t.test2[0] = 0xad; ... // Does not need to be swapped t.test3[0] = 0xbe; ... // Does not need to be swapped t.test4 = htons(0xdeca); // Needs to be swapped ... sendto(..., &t, ...); Receiver: --------- recvfrom(..., &t, ...); t.test4 = ntohs(0xdeca); // Needs to be swapped 

使用htons()ntohs()使用以太网字节排序… big endian。 因此,你的little-endian机器字节交换t.test4并且在收到大端机器时只使用该值读取( ntohs()是有效的noop)。

下图将使这一点更清晰…… Endian交换

如果您不想使用htons()函数及其变体,那么您可以在字节级别定义缓冲区格式。 这个图表使这更清楚…… 定义缓冲区的字节格式

在这种情况下,您的代码可能看起来像

 Sender: ------- uint8_t buffer[SOME SIZE]; t.test = 0xde; t.test2[0] = 0xad; ... t.test3[0] = 0xbe; ... t.test4 = 0xdeca; buffer[0] = t.test; buffer[1] = t.test2[0]; /// and so on, until... buffer[7] = t.test4 & 0xff; buffer[8] = (t.test4 >> 8) & 0xff; ... sendto(..., buffer, ...); Receiver: --------- uint8_t buffer[SOME SIZE]; recvfrom(..., buffer, ...); t.test = buffer[0]; t.test2[0] = buffer[1]; // and so on, until... t.test4 = buffer[7] | (buffer[8] << 8); 

无论发送方和接收方的相应字节顺序如何,发送和接收代码都将起作用,因为缓冲区的字节布局是由两台机器上运行的程序定义和识别的。

但是,如果您以这种方式通过sockets发送结构,您还应该注意下面的警告......

问题2:数据对齐

文章“数据对齐:拉直和向右飞”对于这个文章是一个很好的解读...

您可能遇到的另一个问题是数据对齐。 情况并非总是如此,即使在使用不同端序约定的机器之间,但仍然需要注意......

 struct { uint8_t v1; uint16_t v2; } 

在上面的代码位中, v2从结构开始的偏移量可以是1字节,2字节,4字节(或几乎任何东西)。 编译器不能重新排序结构中的成员,但它可以填充变量之间的距离。

可以说机器1有一个16位宽的数据总线。 如果我们采用没有填充的结构,机器将不得不做两次获取以获得v2 。 为什么? 因为我们在h / w级别一次访问2个字节的内存。 因此编译器可以像这样填充结构

 struct { uint8_t v1; uint8_t invisible_padding_created_by_compiler; uint16_t v2; } 

如果发送方和接收方将数据打包到结构中的方式不同,那么仅将结构作为二进制blob发送将导致问题。 在这种情况下,您可能必须在发送之前手动将变量打包到字节流/缓冲区中。 这通常是最安全的方式。

真的没有结构的字节顺序。 这是需要时需要转换为big-endian的所有单独字段。 您可以复制结构并使用hton / htons重写每个字段,然后发送结果。 8位字段当然不需要任何修改。

在TCP的情况下,您也可以单独发送每个部分并依靠nagle算法将所有部分合并为一个数据包,但是使用UDP,您需要预先准备好所有部分。

无论所涉及机器的最终用语如何,您通过网络发送的数据都应相同。 你需要研究的关键词是序列化 。 这意味着将数据结构转换为要通过网络发送或保存到磁盘的一系列位/字节,无论是架构还是编译器,都将始终保持相同。