linux网络编程(六)主机字节序和网络字节序
由于网络的特点是将Internet上不同的网络设备和主机进行连接和通信,这决定使用网络进行开发的程序的特点就是要兼容各种类型的设备,其中的数据在不同的设备上要有唯一的含义。字节序的问题是上述情况下的典型问题。
一、字节序的含义
字节序的问题是由于CPU对整数在内存中的存放方式造成的。多于一个字节的数据类型在内存中的存放顺序叫主机字节序。最常见的字节序有两种,小端字节序和大端字节序。
- 小端字节序:即Little Endian,简称LE,将数据的最低字节放在内存的起始位置。小端字节序的特点是内存地址较低的位存放数据的低位,内存地址高的位存放数据的高位,与思维习惯一致。采用低字节序的CPU有x86架构的Intel系列产品。
- 大端字节序:即Big Endian,简称BE,将数据的高字节放在内存的起始位置。 大端字节序的特点是内存中低字节位置存放数据的高位字节,内存中的高位字节存放数据的较低字节数据,与思维习惯不一致。 但是与实际数据的表达方式是一致 的。 如果将内存中的数据直接存放在文件中,打开文件查看会发现和 原来的数据的高低位一致。 采用大端字节序的典型的代表有Power PC的 UNIX系统。
例如,对于一个 8 位字节的数据Ox12345678,假设在内存中存放的开始地址为Ox1000, 则在小端字节序系统和大端字节序系统中的方式如表5.8所示。
而如果将Ox12345678写入内存地址Ox1000开始的地方,在内存中的值为表5.9所示的形式 。

系统对多字节数据的不同存放方法造成了使用方法的问题,例如,在x86系统主机A上的一个值为0x12345678,数据通过网络传送到 Power PC上的一个运行 UNIX的主机B上,在B上此值解释为0x78563412,与原来的数据迥异,这样就造成了传输上兼容性方面的困难。
二、网络字节序的转换
网络的字节序标准规定为大端字节序,不同平台上会对主机字节序进行转化后再进行传送,到主机后再转化为主机字节序,数据的传输就不会产生传输造成的问题了。 同一个数据在不同的平台上可以使用网络字节序的转换函数来实现。
如图5.31所示为主机A中的应用程序将变量a中的值0xl2345678, 通过网络传递给主机B中的应用程序中的变量b,如果不进行网络字节序转换,b的值为0x78563412。
如图5.32所示,如果进行网络字节序转换,a的值与b的值均为0x12345678。
进行网络字节序转换的函数有htons()、ntohs()、htonl()、ntohl()等,其中 s是short数据类型的意思,l是long数据类型的意思,而h是host,即主机的意思,n是network,即网络的意思。

以上4个函数分别如下:
- htons():表示对于 short 类型的变量,从主机字节序转换为网络字节序。
- ntohs():表示对于 short 类型的变量,从网络字节序转换为主机字节序。
- htonl():表示对于 long 类型的变量,从主机字节序转换为网络字节序。
- ntohl():表示对于 long 类型的变量,从网络字节序转换为主机字节序。
字节序的转换函数并没有转换符号类型变量,是否为符号类型是由应用程式来确定的, 与字节序无关。
字节序转换函数在不同平台上的实现是不同的,如对于 long 类型的转换,小端主机字节序的平台要进行转换,而在大端主机字节序的平台上是不需要进行转换的。例如下面的 实现方式可以兼容不同的平台:
字节序交换的作用是生成一个网络字节序的变量,其字节的顺序与主机类型和操作系 统无关。进行网络字节序转换的时候,只要转换一次就可以了,不要进行多次的转换。如果进行多次字节序的转换,最后生成的网络字节序的值可能是错误的。例如,对于主机为 小端字节序的系统,进行两次字节序转换的过程如图8.5所示,经过两次转换,最终的值与最初的主机字节序相同。

不同的平台的实现代码是不同的部分。其他函数的实现与此类似,注意htons()和ntohs()函数及 htonl() 和 ntohl() 函数是对应的转换,两个函数完全可以使用同一套代码,例如:
#define ntohl htonl
扩展
## 字符串IP地址和二进制IP地址的转换
inet_xxx() 函数
Linux 操作系统有一组函数用于网络地址的字符串形式和二进制形式之间的转换, 其形式为 inet_xxx()。函数的原型如下:

inet_pton()和inet_ntop()函数
inet_pton()函数和inet_ntop()函数是一套安全的协议无关的地址转换函数。所谓的"安全"是相对于inet_aton()函数的不可重入性来说 。这两个函数都是可以重入的,并且这些函数支持多种地址类型,包括IPv4和1Pv6。
1. inet_pton() 函数
inet_pton()函数将字符串类型的IP地址转换为二进制类型,其原型如下。第1个参数 af表示网络类型的协议族,在1Pv4下的值为AFINET;第2个参数src表示需要转换的字符串;第3个参数dst指向转换后的结果,在IPv4下,dst指向结构struct in_addr的指针。

当函数inet_pton()的返回值为-1的时候,通常是由于af所指定的协议族不支持造成的,此时ermo的返回值为EAFNOSUPPORT;当函数的返回值为 0 时,表示src指向的值不是合法的E地址;当函数的返回值为正值时,表示转换成功。
2.inet_ntop() 函数
inet_ntop()函数将二进制的网络IP地址转换为字符串,函数原型如下所示。第1个参数af表示网络类型的协议族,在IPv4下的值为AF_INET;第2个参数src为需要转换的二进制 IP 地址,在 IPv4 下,src指向一个struct in_addr 结构类型的指针;第3个参数dst 指向保存结果缓冲区的指针;第4个参数cnt的值是dst缓冲区的大小。

inet_ntop() 函数返回一个指向dst的指针。当发生错误时,返回 NULL。当 af 设定的协议族不支持时,errno设置为EAFNOSUPPORT;当dst 缓冲区过小的时候errno 的值为ENOSPC。
## 协议名称处理函数
xxxprotoxxx() 函数
协议族处理函数有如下几个, 可以通过协议的名称、 编号等获取协议类型。
上面的函数对文件/etc/protocols 中的记录进行操作,文件中记录了协议的名称、 值和别名等值。 与结构 struct protoent 的定义一致。结构 protoent 的定义如下:

- 成员p_name为指向协议名称的指针。
- 成员p_aliases是指向别名列表的指针,协议的别名是一个字符串。
- 成员p_proto是协议的值。
浙公网安备 33010602011771号