网络编程中的主机字节序与网络字节序

来源:互联网 发布:万网域名转移 编辑:程序博客网 时间:2024/05/19 10:42

一、大端、小端
"大端"和"小端"表示多字节值的哪一端存储在该值的起始地址处;小端存储在起始地址处,即是小端字节序;大端存储在起始地址处,即是大端字节序;
或者说:
1.小端法(Little-Endian)就是低位字节排放在内存的低地址端(即该值的起始地址),高位字节排放在内存的高地址端;
2.大端法(Big-Endian)就是高位字节排放在内存的低地址端(即该值的起始地址),低位字节排放在内存的高地址端;
举个简单的例子,对于整型数据0x12345678,它在大端法和小端法的系统中,各自的存放方式如下图1所示:


二、网络字节序
网络上传输的数据都是字节流,对于一个多字节数值,在进行网络传输的时候,先传递哪个字节?也就是说,当接收端收到第一个字节的时候,它将这个字节作为高位字节还是低位字节处理,是一个比较有意义的问题;
UDP/TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,这就要求发送端发送的第一个字节是高位字节;而在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个字节,也就是说,该数值在内存中的起始地址处对应的那个字节就是要发送的第一个高位字节(即:高位字节存放在低地址处);由此可见,多字节数值在发送之前,在内存中因该是以大端法存放的;
所以说,网络字节序是大端字节序;
比如,我们经过网络发送整型数值0x12345678时,在80X86平台中,它是以小端发存放的,在发送之前需要使用系统提供的字节序转换函数htonl()将其转换成大端法存放的数值;
如下图2所示:
一般华为规定再进行网络传输的时候,不管主机端是什么,都转换一下,这个也是对的。因为一般网络协议的实现中都会对htonl等的实现有所明确,if系统是大端,那么htonl(a)=a;else htonl(a)进行字节转换。看一个例子:
linux内核源码里就有如此处理。
/usr/include/netinet/in.h中
#include <endian.h>
/* Get machine dependent optimized versions of byte swapping functions.  */
#include <bits/byteswap.h>
#ifdef __OPTIMIZE__
/* We can optimize calls to the conversion functions.  Either nothing has
   to be done or we are using directly the byte-swapping functions which
   often can be inlined.  */
# if __BYTE_ORDER == __BIG_ENDIAN
/* The host byte order is the same as network byte order,
   so these functions are all just identity.  */
# define ntohl(x) (x)
# define ntohs(x) (x)
# define htonl(x) (x)
# define htons(x) (x)
# else
#  if __BYTE_ORDER == __LITTLE_ENDIAN
#   define ntohl(x) __bswap_32 (x)
#   define ntohs(x) __bswap_16 (x)
#   define htonl(x) __bswap_32 (x)
#   define htons(x) __bswap_16 (x)
#  endif
# endif
#endif
也就是说如果是大端CPU,那么不用变,如果是小端CPU,就交换。
但是在我近期研究的工业以太网协议里面,SOEM的作者定义的标准却跟这个公认的TCP/IP网络标准是反的,下面的例子是在其中一个头文件中的定义:
#define LITTLE_ENDIAN
...
...
#if !defined(LITTLE_ENDIAN) && defined(BIG_ENDIAN)
  #define htoes(A) (A)
  #define htoel(A) (A)
  #define etohs(A) (A)
  #define etohl(A) (A)
 #elif !defined(BIG_ENDIAN) && defined(LITTLE_ENDIAN)
  #define htoes(A) ((((uint16)(A) & 0xff00) >> 8) | /
                    (((uint16)(A) & 0x00ff) << 8))
  #define htoel(A) ((((uint32)(A) & 0xff000000) >> 24) | /
                    (((uint32)(A) & 0x00ff0000) >> 8)  | /
                    (((uint32)(A) & 0x0000ff00) << 8)  | /
                    (((uint32)(A) & 0x000000ff) << 24))
  #define etohs  htoes
  #define etohl  htoel
#endif
so,不管怎么样,只要理解了这个关系就可以,以不变应万变。
下面我们测试一下。
 三、字节序测试
不同CPU平台上字节序通常也不一样,下面这个简单的代码可以测试不同平台上的字节序:
#include <stdio.h>
#include <netinet/in.h>
int main(int argc,char** argv)
{
  int num = 0x12345678;
  unsigned char* pc = (unsigned char*)(&num);
  printf("local order:/n");
  printf("[0]: 0x%X addr:%u/n", pc[0], &pc[0]);
  printf("[1]: 0x%X addr:%u/n", pc[1], &pc[1]);
  printf("[2]: 0x%X addr:%u/n", pc[2], &pc[2]);
  printf("[3]: 0x%X addr:%u/n", pc[3], &pc[3]);
  num = htonl(num);
  printf("htonl order:/n");
  printf("[0]: 0x%X addr:%u/n", *pc, &pc[0]);
  printf("[1]: 0x%X addr:%u/n", *(pc+1), &pc[1]);
  printf("[2]: 0x%X addr:%u/n", *(pc+2), &pc[2]);
  printf("[3]: 0x%X addr:%u/n", *(pc+3), &pc[3]);
  return 0;
}
SPARC和PowerPC平台上的输出:
local order:
[0]: 0x12 addr:4290770212 //高位字节存放在低地址处,则是大端法;
[1]: 0x34 addr:4290770213
[2]: 0x56 addr:4290770214
[3]: 0x78 addr:4290770215 //低位字节存放在高地址处;
htonl order:
[0]: 0x12 addr:4290770212 //由此看出,主机字节序与网络字节一样;
[1]: 0x34 addr:4290770213
[2]: 0x56 addr:4290770214
[3]: 0x78 addr:4290770215
Intel X86平台上的输出:
local order:
[0]: 0x78 addr:4289157020 //低位字节存放在低地址处,则是小端法;
[1]: 0x56 addr:4289157021
[2]: 0x34 addr:4289157022
[3]: 0x12 addr:4289157023 //高位字节存放在高地址处;
htonl order:
[0]: 0x12 addr:4289157020 //由此看出,主机字节序与网络字节不一样;
[1]: 0x34 addr:4289157021
[2]: 0x56 addr:4289157022
[3]: 0x78 addr:4289157023