网络基础-字节序 网络和主机字节序 htons() ntoh

英文版发表在:holmeshe.me

我在读一些网络系统的源码时,htons()和 ntohs() 是两个最开始困扰我的函数。所以我决定要重新学一下大学里的知识-字节序。

网络和主机字节序

字节序控制了一个字(word)是怎么存储在内存里,以及怎么在网络上传输的。在big-endian中,最高位的byte被存储在最低的地址,而在little-endian中,最高位则存在最高的地址。

正着

正着

反着

在物理媒介上放字节和铺地板一样,正着铺或者反着铺都可以。但是一个系统设计师就需要做个决定,来保证风格一致了。RFC 1700里面规定,网络协议里面都使用big-endian 字节序,但是一些主机的设计师明显不同意。X86使用little-endian;而ARM则两种都有可能。这就意味着同一个数据,在不同的系统中存放的实际值是不一样的。比如说,A1B2C3D4 (写成十进制是 2712847316)就会有两种存放方式,

在上图中,每一个框,比如那个A1 的框,代表一个字节。这里要注意,这个字“节序”是以“字节” (byte)而不是位(bit)为单位的。

其实计算机处理两种字节序都可以,但是人类就总会抱怨little-endian反了。为啥呢?难道把低位字节设置给低地址,高位字节给高地址不是最合乎逻辑的做法吗?

其实,这个是因为我们在纸上(另一种物理媒介)写这些数字时会在潜意识里用big-endian。还是那上面的数字举例(A1B2C3D4),我们的潜意识会在这个数字周围画上横纵坐标,

如果我们指挥潜意识从右往左画这个横坐标,也许我们就可以解决这个直觉和理性之间的矛盾了。

我觉得这么做也没啥不对的,毕竟在实际开发中本来就存在各种各样的坐标系,比如:

你觉得呢?

好了,下面我们来看看为啥这些理论这么重要,并且在实际中都是咋使用的。

htons() ntohs()

这两个函数就是用来解决网络和主机字节序不一致的问题。技术上来说,当主机在网络上通信时,这两个函数负责转换主机和网络字节序。如果主机和网络字节序是一样的(这代表主机和网络都用big-endian),这两个函数啥都不做。而当主机和网络字节序不一致的话,htons() 把little-endian转换成big-endian,而ntohs()把big-endian转换回为little-endian。

类似的还有一对函数,htonl()和ntohl()。这两个函数除了处理的数字比较大以外,其他的都和htons(),ntohs()一样,就不赘述了。

验证一下

[cpp] view plain copy print?

  1. #include <stdio.h>
  2. #define BITS_PER_BYTE 8
  3. int main() {
  4. unsigned int anint = 0xa1b2c3d4;
  5. unsigned char *truth = &anint;
  6. printf(“value in decimal: %u\n”, anint);
  7. printf(“0x”);
  8. for (int i = 0; i < sizeof(anint); i++) {
  9. printf(“%2X”, truth[i]);
  10. }
  11. printf(“\n”);
  12. unsigned int anint_net = htons(anint);
  13. truth = &anint_net;
  14. printf(“value in decimal after hton: %u\n”, anint_net);
  15. printf(“0x”);
  16. for (int i = 0; i < sizeof(anint_net); i++) {
  17. printf(“%02X”, truth[i]);
  18. }
  19. printf(“\n”);
  20. }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cpp复制代码#include <stdio.h>
#define BITS_PER_BYTE 8
int main() {
unsigned int anint = 0xa1b2c3d4;
unsigned char *truth = &anint;
printf("value in decimal: %u\n", anint);
printf("0x");
for (int i = 0; i < sizeof(anint); i++) {
printf("%2X", truth[i]);
}
printf("\n");
unsigned int anint_net = htons(anint);
truth = &anint_net;
printf("value in decimal after hton: %u\n", anint_net);
printf("0x");
for (int i = 0; i < sizeof(anint_net); i++) {
printf("%02X", truth[i]);
}
printf("\n");
}

结果是(在我的测试机上): [html] view plain copy print?

  1. value in decimal: 2712847316
  2. 0xD4C3B2A1
  3. value in decimal after hton: 54467
  4. 0xC3D40000
1
2
3
4
dns复制代码value in decimal: 2712847316
0xD4C3B2A1
value in decimal after hton: 54467
0xC3D40000

正如上述��,htons()把anint的原始的值转换成big-endian,用于准备网络传输。这个值会在接收方用ntohs()恢复成原始值。虽然这个接受方的部分没有展示,但是我相信你可以懂的。

咋判断字节序

我们可以复用上面��中的代码来实现一个函数来判断主机的字节序:

[cpp] view plain copy print?

  1. int isLittle() {
  2. unsigned int anint = 0xa1b2c3d4;
  3. unsigned char *truth = &anint;
  4. return truth[0] == 0xd4;
  5. }
1
2
3
4
5
cpp复制代码int isLittle() {
unsigned int anint = 0xa1b2c3d4;
unsigned char *truth = &anint;
return truth[0] == 0xd4;
}

实际上还有一个更简单的办法, [plain] view plain copy print?

  1. cpu | grep “Byte Order”
1
1c复制代码cpu | grep "Byte Order"

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%