我认为这里可能存在两个问题,具体取决于您如何通过 TCP 发送这些数据。
问题 1:字节序
正如你所说的字节顺序是一个问题。当您提到使用htons
和ntohs
短裤时,您是对的。您可能还会发现htonl
它的反面也很有用。
字节序与内存中多字节数据类型的字节顺序有关。因此,对于单字节宽度的数据类型,您不必担心。在您的情况下,我猜您正在质疑的是 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);
无论发送者和接收者各自的字节顺序如何,发送和接收代码都将起作用,因为缓冲区的字节布局是由运行在两台机器上的程序定义和知道的。
但是,如果您以这种方式通过套接字发送结构,则还应注意以下警告...
问题 2:数据对齐
文章“数据对齐:直起直飞”是这篇文章的好读物……
您可能遇到的另一个问题是数据对齐。情况并非总是如此,即使在使用不同字节序约定的机器之间也是如此,但仍然需要注意......
struct
{
uint8_t v1;
uint16_t v2;
}
在上面的代码中,v2
从结构开始的偏移量可以是 1 字节、2 字节、4 字节(或几乎任何东西)。编译器无法重新排序结构中的成员,但它可以填充变量之间的距离。
假设机器 1 有一个 16 位宽的数据总线。如果我们在没有填充的情况下采用结构,机器将不得不执行两次 fetches 来获取v2
. 为什么?因为我们在硬件级别一次访问 2 个字节的内存。因此编译器可以像这样填充结构
struct
{
uint8_t v1;
uint8_t invisible_padding_created_by_compiler;
uint16_t v2;
}
如果发送方和接收方在将数据打包到结构中的方式不同,那么仅将结构作为二进制 blob 发送就会给您带来问题。在这种情况下,您可能必须在发送之前手动将变量打包到字节流/缓冲区中。这通常是最安全的方法。