反转C结构的端点

反转C结构的端点,c,endianness,C,Endianness,我有一个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(

我有一个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将此结构发送到服务器。目前,当我在本地进行测试时,这可以正常工作,但是我现在需要将此结构从我的
小端机
发送到
大端机
机器。我真的不知道该怎么做


我已经研究过如何使用
htons
,但我不确定这是否适用于这种情况,因为如果我理解正确,它似乎只适用于16位或32位的
无符号整数。需要时,需要将所有单独的字段转换为big-endian。您可以复制结构并使用
hton
/
htons
重写每个字段,然后发送结果。8位字段当然不需要任何修改


对于TCP,您也可以单独发送每个部分,并依靠nagle算法将所有部分合并到一个数据包中,但对于UDP,您需要提前准备好所有内容。

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


对于TCP,您也可以单独发送每个部分,并使用nagle算法将所有部分合并到一个数据包中,但对于UDP,您需要提前准备所有内容。

我认为,根据您通过TCP发送数据的方式,这里可能存在两个问题

问题1:持久性 正如你所说,endianness是一个问题。你提到用
htons
ntohs
做短裤是对的。您可能还会发现
htonl
及其对立面也很有用

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

要使用这些函数,您需要执行以下操作

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()
使用以太网字节排序。。。大恩迪安。因此,您的小端机器字节交换
t.test4
,收到后,大端机器只使用读取的值(
ntohs()
实际上是一个noop)

下面的图表将使这一点更加清楚。。。

如果不想使用
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);
在上面的代码位中,
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的形式发送该结构就会给您带来问题。在这种情况下,在发送之前,您可能必须手动将变量打包到字节流/缓冲区中。这通常是最安全的方式。

我认为,根据您通过TCP发送数据的方式,这里可能存在两个问题

问题1:持久性 正如你所说,endianness是一个问题。你提到用
htons
ntohs
做短裤是对的。您可能还会发现
htonl
及其对立面也很有用

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

要使用这些函数,您需要执行以下操作

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()
使用以太网字节排序。。。大恩迪安。因此,您的小端机器字节交换
t.test4
,收到后,大端机器只使用读取的值(
ntohs()
实际上是一个noop)

下面的图表将使这一点更加清楚。。。

如果不想使用
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);
在上面的代码位中,
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的形式发送该结构就会给您带来问题。在这种情况下,在发送之前,您可能必须手动将变量打包到字节流/缓冲区中。这通常是最安全的方式。

无论所涉及的机器的终端如何,通过网络发送的数据都应该是相同的。您需要研究的关键词是序列化。这意味着将数据结构转换为系列