字节序问题

htons(), ntohl(), ntohs(),htons() 函数

   在C/C++写网络程序的时候,往往会遇到字节的网络顺序和主机顺序的问题。这是就可能用到htons(), ntohl(), ntohs(),htons()这4个函数。

网络字节顺序与本地字节顺序之间的转换函数:

      htonl()--"Host to Network Long"
      ntohl()--"Network to Host Long"
      htons()--"Host to Network Short"
      ntohs()--"Network to Host Short"

之所以需要这些函数是因为计算机数据表示存在两种字节顺序:NBO与HBO

网络字节顺序NBO(Network Byte Order):
      按从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题。

主机字节顺序(HBO,Host Byte Order):
      不同的机器HBO不相同,与CPU设计有关,数据的顺序是由cpu决定的,而与操作系统无关。 
如 Intel   x86结构下,short型数0x1234表示为34   12, int型数0x12345678表示为78   56   34   12   
如IBM   power PC结构下,short型数0x1234表示为12   34, int型数0x12345678表示为12   34   56   78
   
      由于这个原因不同体系结构的机器之间无法通信,所以要转换成一种约定的数序,也就是网络字节顺序,其实就是如同power   pc那样的顺序 。在PC开发中有ntohl和htonl函数可以用来进行网络字节和主机字节的转换。


1       字节序
由于不同的计算机系统采用不同的字节序存储数据,同样一个4字节的32位整数,在内存中存储的方式就不同. 字节序分为小尾字节序(Little Endian)和大尾字节序(Big Endian), Intel处理器大多数使用小尾字节序, Motorola处理器大多数使用大尾(Big Endian)字节序;
小尾就是低位字节排放在内存的低端,高位字节排放在内存的高端。例如一个4字节的值为0x1234567的整数与高低字节对应关系:
01
23
45
67
Byte3
Byte2
Byte1
Byte0
高位字节-- à --------- à -------------- à 低位字节
 
将在内存中按照如下顺序排放:
内存地址序号
字节在内存中的地址
16 进制值
0x03
Byte3
01
0x02
Byte2
23
0x01
Byte1
45
0x00
Byte0
67
 
 
大尾就是高位字节排放在内存的低端,低位字节排放在内存的高端。例如一个4字节的值为0x1234567的整数与高低字节对应关系:
01
23
45
67
Byte3
Byte2
Byte1
Byte0
高位字节-- à --------- à -------------- à 低位字节
将在内存中按照如下顺序排放:
内存地址序号
字节在内存中的地址
16 进制值
0x03
Byte0
67
0x02
Byte1
45
0x01
Byte2
23
0x00
Byte3
01
 
2       网络字节序
TCP/IP 各层协议将字节序定义为大尾,因此TCP/IP协议中使用的字节序通常称之为网络字节序。
3       字串在内存中的存储(intel系列)
     字串和整数是相反的,是安字串的索引从低到高存储到内存中的;
     char s[4] =  “abc”;  
a
b
c
/0
s[0]
s[1]
s[2]
s[3]
 
 
将在内存中按照如下顺序排放:
内存地址序号
16 进制值
指针P的位置
0xbffeadf7
/0
p+3
0xbffeadf6
c
p+2
0xbffeadf5
b
p+1
0xbffeadf4
a
p
 
int main(void)
{
     char s[4] = "abc";
     char *p = s; 
     printf("%02x, %02x,    %02x,    %02x/n", &s[0], &s[1], &s[2], &s[3]);
     printf("%02x, %02x,    %02x,    %02x/n", p, p+1, p+2, p+3);
     printf("%c,   %c, %c, %c/n", s[0], s[1], s[2], s[3]);
        
     return 0;
}
输出结果:
[netcool@HFINMSP2 demo]$ ./demo001
bffeadf4,       bffeadf5,       bffeadf6,       bffeadf7
bffeadf4,       bffeadf5,       bffeadf6,       bffeadf7
a,      b,      c,
4       整数数组在内存中的存储(intel系列)
同字串一样,但是数组里的每一个整数的存储是按照小尾字节序;
 
5       linux系统中的处理方法
网络字节序作为一个标准字节序,如果系统并没有提供相关的转换函数,我们可以通过以下4个宏实现本地字节序和网络字节序的相互转换:
htons(): 将16位无符号整数从本地字节序转换成网络字节序
htonl(): 将32位无符号整数从本地字节序转换成网络字节序
ntohs(): 将16位无符号整数从网络字节序转换成本地字节序
ntohl(): 将32位无符号整数从网络字节序转换成本地字节序
 

一、字节序定义

字节序,顾名思义字节的顺序,再多说两句就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)。

其实大部分人在实际的开发中都很少会直接和字节序打交道。唯有在跨平台以及网络程序中字节序才是一个应该被考虑的问题。

在所有的介绍字节序的文章中都会提到字节序分为两类:Big-Endian和Little-Endian。引用标准的Big-Endian和Little-Endian的定义如下:
a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
c) 网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于 TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。比如,以太网头部中2字节的“以太网帧类型”,表示后面数据的类型。对于ARP请求或应答的以太网帧类型来说,在网络传输时,发送的顺序是0x08,0x06。在内存中的映象如下图所示:
栈底 (高地址)
---------------
0x06 -- 低位 
0x08 -- 高位
---------------
栈顶 (低地址)
该字段的值为0x0806。按照大端方式存放在内存中。

二、高/低地址与高低字节

首先我们要知道我们C程序映像中内存的空间布局情况:在《C专家编程》中或者《Unix环境高级编程》中有关于内存空间布局情况的说明,大致如下图:
----------------------- 最高内存地址 0xffffffff
 | 栈底
 .
 .              栈
 .
  栈顶
-----------------------
 |
 |
\|/

NULL (空洞)

/|\
 |
 |
-----------------------
                堆
-----------------------
未初始化的数据
----------------(统称数据段)
初始化的数据
-----------------------
正文段(代码段)
----------------------- 最低内存地址 0x00000000

以上图为例如果我们在栈上分配一个unsigned char buf[4],那么这个数组变量在栈上是如何布局的呢[注1]?看下图:
栈底 (高地址)
----------
buf[3]
buf[2]
buf[1]
buf[0]
----------
栈顶 (低地址)

现在我们弄清了高低地址,接着来弄清高/低字节,如果我们有一个32位无符号整型0x12345678(呵呵,恰好是把上面的那4个字节buf看成一个整型),那么高位是什么,低位又是什么呢?其实很简单。在十进制中我们都说靠左边的是高位,靠右边的是低位,在其他进制也是如此。就拿 0x12345678来说,从高位到低位的字节依次是0x12、0x34、0x56和0x78。

高低地址和高低字节都弄清了。我们再来回顾一下Big-Endian和Little-Endian的定义,并用图示说明两种字节序:
以unsigned int value = 0x12345678为例,分别看看在两种字节序下其存储情况,我们可以用unsigned char buf[4]来表示value:
Big-Endian: 低地址存放高位,如下图:
栈底 (高地址)
---------------
buf[3] (0x78) -- 低位
buf[2] (0x56)
buf[1] (0x34)
buf[0] (0x12) -- 高位
---------------
栈顶 (低地址)

Little-Endian: 低地址存放低位,如下图:
栈底 (高地址)
---------------
buf[3] (0x12) -- 高位
buf[2] (0x34)
buf[1] (0x56)
buf[0] (0x78) -- 低位
---------------
栈顶 (低地址)

在现有的平台上Intel的X86采用的是Little-Endian,而像Sun的SPARC采用的就是Big-Endian。

三、例子

嵌入式系统开发者应该对Little-endian和Big-endian模式非常了解。采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低字节。

例如,16bit宽的数0x1234在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址  存放内容
 0x4001    0x12
 0x4000    0x34

而在Big-endian模式CPU内存中的存放方式则为:

内存地址  存放内容
 0x4001    0x34
 0x4000    0x12
 
32bit宽的数0x12345678在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址  存放内容
 0x4003     0x12
 0x4002     0x34
 0x4001     0x56
 0x4000     0x78
 
而在Big-endian模式CPU内存中的存放方式则为:

内存地址  存放内容
 0x4003     0x78
 0x4002     0x56
 0x4001     0x34
 0x4000     0x12



linux字节序转换函数和地址转换函数

2009-10-12 20:09 by cesc711, 824 阅读, 0 评论, 收藏编辑
刚才阅读代码的时候看到了一个字节排序函数,一时想不起具体用法了。想想学习linux下的网络编程也不少时间了,这些字节排序和转换的函数还是不太清楚,容易混淆。今天索性把这方面的知识汇总一下,争取以后能够熟练的认识和运用。
字节顺序函数:
小端字节序:将低序字节存储在起始地址。(linux)
大端字节序:将高序字节存储在起始地址。(网络字节序)
举个例子:对于整数0x12345678来说,在不同的系统中存放的方式如下图:
  
正是因为网际协议采取的是大端字节序,我们在编程的时候才需要考虑网络字节许和主机字节序之间的转换。下面是四个转换函数(在某些采用大端字节序的系统里面,这四个函数被定位空宏):

#include  < netinet / in .h >
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);  
// 均返回网络字节序

uint16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue);   
// 均返回主机字节序

地址转换函数:
下面介绍两组地址转换函数。首先是第一组:

复制代码
#include  < arpa / inet.h >
int    inet_aton( const   char   * strptr,  struct  in_addr  * addrptr);
                                        
// 返回:1——串有效,0——串有错
char inet_ntoa( struct  in_addr inaddr);
                                        
// 返回一个指向ASCII字串的指针
in_addr_t inet_addr( const   char   * strptr);
                                        
// 成功返回网络字节序,错误返回INADDR_NONE
复制代码

接下来是第二组,这两个函数比较新,对于IPv4和IPv6都能处理。p的含义是presentation,代表ASCII字串。n的含意是numberic,代表网络地址值。

#include  < netinet / in .h >
#define  INET_ADDRSTRLEN  16
#define  INET6_ADDRSTRLEN 46

#include  < arpa / inet.h >
int  inet_pton( int  family,  const   char   * strptr,  void   * addrptr);
                                  
// 返回:1——成功,输入的不是有效表达格式,-1——出错
const   char *  inet_ntop( int  family,  const   void   * addrptr,  char   * strptr, size_t len);
                                  
// 返回:指向结果的指针——成功,NULL——出错                 

在计算机科学领域中,字节序是指存放多字节数据的字节(byte)的顺序,典型的情况是整数在内存中的存放方式和网络传输的传输顺序。不同的处理器所采用的字节序可能是不同的,例如: x86,6502Z80VAX,和 PDP-11都是采用小端字节序,而 Motorola 6800 、 68kIBM POWER, 和 System/360则采用大端字节序。另外,网络协议通常也会规定其所采用的字节序,还有像java这样的语言,也是规定了字节序的(tcp/ip和java都是采用大端字节序)。


通常,系统中会提供ntohs、htons、ntohl、htonl这4个函数,已实现16位和32位本地字节序和网络字节序的转换。但是,目前好像还没有提供64位数据字节序的转换函数。所以,在这里自己动手写一个。


首先,我们要判断本地系统所采用的字节序:

[cpp]  view plain copy
  1. #define BigEndian 1  
  2. #define LittleEndian 0  
  3. static bool BigEndianTest()  
  4. {  
  5.     /*定义一个2个字节长度的数据,并赋值为1,则n的16进制表示为0x0001 
  6.     如果系统以“大端”存放数据,也即是以MSB方式存放,那么低字节存放的必定是0x00,高字节存放的必定是0x01 
  7.     如果系统以“小端”存放数据,也即是以LSB方式存放,那么低字节存放的必定是0x01,高字节存放的必定是0x00 
  8.     所谓MSB,就是将最重要的位存入低位,而LSB则是将最不重要的位存入低位 
  9.     我们可以通过检测低位的数值就可以知道系统的字节序 
  10.     */  
  11.     const __int16 n = 1;  
  12.     if(*(char *)&n)  
  13.     {  
  14.         return LittleEndian;  
  15.     }  
  16.     return BigEndian;  
  17. }  
 

当然这里可以再优化一下,写成宏定义。

 

然后,定义16、32、64位的调位函数。这里就是字节“搬家”而已。

[cpp]  view plain copy
  1. #define Swap16(s) ((((s) & 0xff) << 8) | (((s) >> 8) & 0xff))  
  2. #define Swap32(l) (((l) >> 24) | /  
  3.            (((l) & 0x00ff0000) >> 8)  | /  
  4.            (((l) & 0x0000ff00) << 8)  | /  
  5.            ((l) << 24))  
  6. #define Swap64(ll) (((ll) >> 56) |/  
  7.                     (((ll) & 0x00ff000000000000) >> 40) |/  
  8.                     (((ll) & 0x0000ff0000000000) >> 24) |/  
  9.                     (((ll) & 0x000000ff00000000) >> 8)    |/  
  10.                     (((ll) & 0x00000000ff000000) << 8)    |/  
  11.                     (((ll) & 0x0000000000ff0000) << 24) |/  
  12.                     (((ll) & 0x000000000000ff00) << 40) |/  
  13.                     (((ll) << 56)))  
 

最后,

[cpp]  view plain copy
  1. #define BigEndian_16(s) BigEndianTest() ? s : Swap16(s)  
  2. #define LittleEndian_16(s) BigEndianTest() ? Swap16(s) : s  
  3. #define BigEndian_32(l) BigEndianTest() ? l : Swap32(l)  
  4. #define LittleEndian_32(l) BigEndianTest() ? Swap32(l) : l  
  5. #define BigEndian_64(ll) BigEndianTest() ? ll : Swap64(ll)  
  6. #define LittleEndian_64(ll) BigEndianTest() ? Swap64(ll) : ll  
 

现在,我们来测试一下:

 

[cpp]  view plain copy
  1. int main()  
  2. {  
  3.     unsigned __int16 i16 = 0xabcd;  
  4.     unsigned __int32 i32 = 0x0a0b0c0d;  
  5.     unsigned __int64 i64 = 0x0102030405060708;  
  6.     printf("System is %s/n",BigEndianTest() ? "BigEndian" : "LittleEndian" );  
  7.     printf("__int16 i16 = 0x%x, BigEndian:0x%x htons:0x%x, LittleEndian:0x%x ntohs:0x%x/n",  
  8.                     i16,BigEndian_16(i16),htons(i16),LittleEndian_16(i16),ntohs(BigEndian_16(i16)));  
  9.     printf("__int32 i32 = 0x%x, BigEndian:0x%x htons:0x%x, LittleEndian:0x%x ntohs:0x%x/n",  
  10.                     i32,BigEndian_32(i32),htonl(i32),LittleEndian_32(i32),ntohl(BigEndian_32(i32)));  
  11.     printf("__int64 i64 = 0x%llx, BigEndian:0x%llx, LittleEndian:0x%llx/n",i64,  
  12.                     BigEndian_64(i64),LittleEndian_64(i64));  
  13.     getchar();  
  14.     return 0;  
  15. }  
 

运行结果如下:

 

[cpp:nogutter]  view plain copy
  1. System is LittleEndian  
  2. __int16 i16 = 0xabcd, BigEndian:0xcdab htons:0xcdab, LittleEndian:0xabcd ntohs:0  
  3. xabcd  
  4. __int32 i32 = 0xa0b0c0d, BigEndian:0xd0c0b0a htons:0xd0c0b0a, LittleEndian:0xa0b  
  5. 0c0d ntohs:0xa0b0c0d  
  6. __int64 i64 = 0x102030405060708, BigEndian:0x807060504030201, LittleEndian:0x102  
  7. 030405060708  



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值