将数据转换为大端序

3
我将使用WinSock向服务器发送UDP数据包,需要以大端模式发送数据。在发送之前,我不确定如何转换结构体的字节顺序。
我有一个类似这样的结构体:
struct ConnectIn
{
    std::int64_t ConnectionID = 0x41727101980;
    std::int32_t Action = 0;
    std::int32_t TransactionID;

    ConnectIn(std::int32_t transactionID)
    {
        TransactionID = transactionID;
    }
};

目前我正在这样发送:

ConnectIn msg(123);
int len = sizeof(msg);
int bytesSent = sendto(s, (char*)&msg, len, 0, (SOCKADDR*)&dest, sizeof(address));

在发送之前,我如何将 msg 的字节顺序转换为大端序?

如果您好奇,我正在发送的数据是用于 Bit Torrent UDP tracker protocol


4
你可以查看 hton 函数族。 - Jarod42
@black - 我不认为这是一个重复的问题。另一个问题是关于 C 而不是 C++。此外,另一个问题是关于序列化或其他什么东西,用词不当,英语糟糕,很难理解。 - Drahcir
将原始结构体通过套接字发送(或存储到文件中等)通常不是一个好主意。由于可能的原因众多,结构体中的某些内容迟早会发生变化,然后您的协议/格式就会破裂。将结构体序列化为字节数组,发送该数组,然后在接收时从接收到的字节数组进行反序列化。 - hyde
你需要首先在位级别上指定你的协议,然后要有信仰地去实现它。你不能直接发送结构体,因为结构体没有通用的位串表示。整数有,所以你需要将各种大小的整数(结构体字段)序列化为位串,并发送它。 - n. m.
好的,所以你需要实现一个现有的协议,只需跳过“指定您的协议”部分即可。 - n. m.
显示剩余2条评论
3个回答

4

如果你想手动完成这个过程,那么你需要逐个交换成员。你需要将成员从主机计算机的字节顺序转换为网络字节顺序。在Win32系统中,htonll() 用于64位整数,htonl() 用于32位整数:

#include <Winsock2.h>

ConnectIn msg(123);

msg.ConnectionID = htonll(msg.ConnectionID);
msg.Action = htonl(msg.Action);
msg.TransactionID= htonl(msg.TransactionID);

如果您想避免依赖主机系统的结构布局,您可能还希望单独发送成员。Windows ABI在此结构中不插入任何填充,但是对于您使用的其他某些结构可能会这样做。因此,这里是基本思路:

char buf[sizeof msg.ConnectionID + sizeof msg.Action + sizeof msg.TransactionID];
char *bufi = buf;

std::memcpy(bufi, &msg.ConnectionID, sizeof msg.ConnectionID);
bufi += sizeof msg.ConnectionID;
std::memcpy(bufi, &msg.Action, sizeof msg.Action);
bufi += sizeof msg.Action;
std::memcpy(bufi, &msg.TransactionID, sizeof msg.TransactionID);
bufi += sizeof msg.TransactionID;

int len = sizeof buf;
int bytesSent = sendto(s, buf, len, 0, (SOCKADDR*)&dest, sizeof(address));

在接收方,您需要使用适当的ntoh*()函数来将64位和32位类型从网络字节顺序转换为接收主机的字节顺序。


你说“如果你想手动做这个”,这是不是意味着有一种替代手动的选择? - Drahcir
@Drahcir 或者你可以使用一些序列化或网络库来为你处理它。 - bames53
1
请注意,您正在使用 sendto 的目标地址参数。这意味着使用数据报协议,如 UDP。也就是说,此答案中的代码发送了 3 个单独的数据包,而不是一个,这可能不是提问者想要的。 - hyde
最终我决定编写自己的模板函数,以便可以交换其他类型的字节,但感谢您提供的一般思路。 - Drahcir
@Drahcir 是的,模板函数是一种很好的实现方式,可以避免重复太多。如果项目中没有现成的方法,这通常是我自己采用的方法。 - bames53

3
是的,网络字节序(NBO)是大端字节序,因此您需要找到一种方法将该结构发送到网络上。
您目前正在做的方式行不通:您正在发送整个结构体,但接收方可能具有不同的字节序、填充等等。
最简单的选项是:
- 使用协议定义的布局发送每个字段 - 使用第三方库处理序列化:Google Protobuf 是最常见的之一。
对于第一种选项,在 Winsock2 库中有一些函数可以处理这个问题。它们是:
- (WSA)ntoh(网络字节序转主机字节序,其中 t 可以是 short 和 unsigned) - (WSA)hton(主机字节序转网络字节序,其中 t 可以是 short 和 unsigned)
WSA 函数略有不同,只能在 Windows 上使用。

网络编程指南
Winsock参考


发送整个结构对于这个协议确实有效,我的结构使用了协议定义的布局(除了字节序),尽管在阅读了每个人的评论后,我可以理解为什么这可能是不好的做法。 - Drahcir
Google Protobuf 看起来很不错,但我认为我不能用它来处理这个问题。我认为服务器也必须修改协议,以接受新的序列化格式,这是正确的吗? - Drahcir
@Drahcir 我认为可能会有,但我不确定。 - edmz

-1

一种选择是将每个数字单独转换

对于GCC:

int32_t __builtin_bswap32 (int32_t x)
int64_t __builtin_bswap64 (int64_t x)

对于 MSVC:

unsigned short _byteswap_ushort(unsigned short value);
unsigned long _byteswap_ulong(unsigned long value);
unsigned __int64 _byteswap_uint64(unsigned __int64 value);

1
那只是你转换数据的方式。不过,那些数据可能已经是网络字节序(NBO)了。 - edmz
2
@lisu:你的平台可能已经是大端字节序,所以 bswap 会将你的数据转换成小端字节序。 - Jarod42
好的,我明白你的意思,但问题特别涉及到转换为大端字节序,所以我没有提到它。 - lisu
@lisu 确实如此。这些函数对BE/LE没有任何了解。 - edmz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接