网络字节序

什么是Big Endian和Little Endian?

来源:http://blog.ednchina.com/qinyonglyz/194674/message.aspx

1.故事的起源

“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。

我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。

2.什么是Big Endian和Little Endian?

在设计计算机系统的时候,有两种处理内存中数据的方法。一种叫为little-endian,存放在内存中最低位的数值是来自数据的最右边部分(也就是数据的最低位部分)。比如一个16进制数字0x12345678,在内存存放的方式如下:
值   
0111,1000   
0101,0110   
0011,0100   
0001,0010

地址   
100   
101   
102   
103


            另一种称为big-endian,正好相反,存放在内存中最低位的数值是来自数据的最左边边部分(也就是数据的最高为部分)。比如一个16进制数字0x12345678,在内存存放的方式如下:
值   
0001,0010   
0011,0100   
0101,0110   
0111,1000

地址   
100   
101   
102   
103


比如某些文件需要在不同平台处理,或者通过Socket通信。这方面我们可以借助ntohl(), ntohs(), htonl(), and htons()函数进行格式转换。

3.如何判断系统是Big Endian还是Little Endian?

在/usr/include/中(包括子目录)查找字符串BYTE_ORDER(或_BYTE_ORDER, __BYTE_ORDER),确定其值。这个值一般在endian.h或machine/endian.h文件中可以找到,有时在feature.h中,不同的操作系统可能有所不同。一般来说,Little Endian系统BYTE_ORDER(或_BYTE_ORDER,__BYTE_ORDER)为1234,Big Endian系统为4321。大部分用户的操作系统(如windows, FreeBsd,Linux)是Little Endian的。少部分,如MAC OS ,是Big Endian 的。本质上说,Little Endian还是Big Endian与操作系统和芯片类型都有关系。

======================================================================

ext3 文件系统在硬盘分区上的数据是按照 Intel 的 Little-endian 格式存放的,如果是在 PC 以外的平台上开发 ext3 相关的程序,要特别注意这一点。

谈到字节序的问题,必然牵涉到两大 CPU派系。那就是Motorola的PowerPC系列CPU和Intel的x86系列CPU。PowerPC系列采用big endian方式存储数据,而x86系列则采用little endian方式存储数据。那么究竟什么是big endian,什么又是little endian呢?

     其实big endian是指低地址存放最高有效字节(MSB),而little endian则是低地址存放最低有效字节(LSB)。

     用文字说明可能比较抽象,下面用图像加以说明。比如数字0x12345678在两种不同字节序CPU中的存储顺序如下所示:

Big Endian

   低地址                                            高地址
   ----------------------------------------->
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |     12     |      34    |     56      |     78    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Little Endian

   低地址                                            高地址
   ----------------------------------------->
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |     78     |      56    |     34      |     12    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

     从上面两图可以看出,采用big endian方式存储数据是符合我们人类的思维习惯的。而little endian,!@#$%^&*,见鬼去吧 -_-|||

     为什么要注意字节序的问题呢?你可能这么问。当然,如果你写的程序只在单机环境下面运行,并且不和别人的程序打交道,那么你完全可以忽略字节序的存在。但是,如果你的程序要跟别人的程序产生交互呢?在这里我想说说两种语言。C/C++语言编写的程序里数据存储顺序是跟编译平台所在的CPU相关的,而 JAVA编写的程序则唯一采用big endian方式来存储数据。试想,如果你用C/C++语言在x86平台下编写的程序跟别人的JAVA程序互通时会产生什么结果?就拿上面的 0x12345678来说,你的程序传递给别人的一个数据,将指向0x12345678的指针传给了JAVA程序,由于JAVA采取big endian方式存储数据,很自然的它会将你的数据翻译为0x78563412。什么?竟然变成另外一个数字了?是的,就是这种后果。因此,在你的C程序传给JAVA程序之前有必要进行字节序的转换工作。

     无独有偶,所有网络协议也都是采用big endian的方式来传输数据的。所以有时我们也会把big endian方式称之为网络字节序。当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为网络字节序后再进行传输。


 
网络字节序与主机字节序


来源:http://www.cnblogs.com/jacktu/archive/2008/11/24/1339789.html
不同的CPU有不同的字节序类型 这些字节序是指整数在内存中保存的顺序 这个叫做主机序
最常见的有两种
1. Little endian:将低序字节存储在起始地址
2. Big endian:将高序字节存储在起始地址

LE little-endian
最符合人的思维的字节序
地址低位存储值的低位
地址高位存储值的高位
怎么讲是最符合人的思维的字节序,是因为从人的第一观感来说
低位值小,就应该放在内存地址小的地方,也即内存地址低位
反之,高位值就应该放在内存地址大的地方,也即内存地址高位

BE big-endian
最直观的字节序
地址低位存储值的高位
地址高位存储值的低位
为什么说直观,不要考虑对应关系
只需要把内存地址从左到右按照由低到高的顺序写出
把值按照通常的高位到低位的顺序写出
两者对照,一个字节一个字节的填充进去

例子:在内存中双字0x01020304(DWORD)的存储方式

内存地址
4000 4001 4002 4003
LE 04 03 02 01
BE 01 02 03 04

例子:如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为
      big-endian  little-endian
0x0000  0x12      0xcd
0x0001  0x23      0xab
0x0002  0xab      0x34
0x0003  0xcd      0x12
x86系列CPU都是little-endian的字节序.

网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用big endian排序方式。

为了进行转换 bsd socket提供了转换的函数 有下面四个
htons 把unsigned short类型从主机序转换到网络序
htonl 把unsigned long类型从主机序转换到网络序
ntohs 把unsigned short类型从网络序转换到主机序
ntohl 把unsigned long类型从网络序转换到主机序

在使用little endian的系统中 这些函数会把字节序进行转换
在使用big endian类型的系统中 这些函数会定义成空宏

同样 在网络程序开发时 或是跨平台开发时 也应该注意保证只用一种字节序 不然两方的解释不一样就会产生bug.

注:
1、网络与主机字节转换函数:htons ntohs htonl ntohl (s 就是short l是long h是host n是network)
2、不同的CPU上运行不同的操作系统,字节序也是不同的,参见下表。
处理器    操作系统    字节排序
Alpha    全部    Little endian
HP-PA    NT    Little endian
HP-PA    UNIX    Big endian
Intelx86    全部    Little endian <-----x86系统是小端字节序系统
Motorola680x()    全部    Big endian
MIPS    NT    Little endian
MIPS    UNIX    Big endian
PowerPC    NT    Little endian
PowerPC    非NT    Big endian  <-----PPC系统是大端字节序系统
RS/6000    UNIX    Big endian
SPARC    UNIX    Big endian
IXP1200 ARM核心    全部    Little endian
=============================================
字节序转换类:

/**
* 通信格式转换
*
* Java和一些windows编程语言如c、c++、delphi所写的网络程序进行通讯时,需要进行相应的转换
* 高、低字节之间的转换
* windows的字节序为低字节开头
* linux,unix的字节序为高字节开头
* java则无论平台变化,都是高字节开头
*/

public class FormatTransfer {
/**
  * 将int转为低字节在前,高字节在后的byte数组
  * @param n int
  * @return byte[]
  */
public static byte[] toLH(int n) {
  byte[] b = new byte[4];
  b[0] = (byte) (n & 0xff);
  b[1] = (byte) (n >> 8 & 0xff);
  b[2] = (byte) (n >> 16 & 0xff);
  b[3] = (byte) (n >> 24 & 0xff);
  return b;
}

/**
  * 将int转为高字节在前,低字节在后的byte数组
  * @param n int
  * @return byte[]
  */
public static byte[] toHH(int n) {
  byte[] b = new byte[4];
  b[3] = (byte) (n & 0xff);
  b[2] = (byte) (n >> 8 & 0xff);
  b[1] = (byte) (n >> 16 & 0xff);
  b[0] = (byte) (n >> 24 & 0xff);
  return b;
}

/**
  * 将short转为低字节在前,高字节在后的byte数组
  * @param n short
  * @return byte[]
  */
public static byte[] toLH(short n) {
  byte[] b = new byte[2];
  b[0] = (byte) (n & 0xff);
  b[1] = (byte) (n >> 8 & 0xff);
  return b;
}

/**
  * 将short转为高字节在前,低字节在后的byte数组
  * @param n short
  * @return byte[]
  */
public static byte[] toHH(short n) {
  byte[] b = new byte[2];
  b[1] = (byte) (n & 0xff);
  b[0] = (byte) (n >> 8 & 0xff);
  return b;
}

 

/**
  * 将将int转为高字节在前,低字节在后的byte数组

public static byte[] toHH(int number) {
  int temp = number;
  byte[] b = new byte[4];
  for (int i = b.length - 1; i > -1; i--) {
    b = new Integer(temp & 0xff).byteValue();
    temp = temp >> 8;
  }
  return b;
}

public static byte[] IntToByteArray(int i) {
    byte[] abyte0 = new byte[4];
    abyte0[3] = (byte) (0xff & i);
    abyte0[2] = (byte) ((0xff00 & i) >> 8);
    abyte0[1] = (byte) ((0xff0000 & i) >> 16);
    abyte0[0] = (byte) ((0xff000000 & i) >> 24);
    return abyte0;
}


*/

/**
  * 将float转为低字节在前,高字节在后的byte数组
  */
public static byte[] toLH(float f) {
  return toLH(Float.floatToRawIntBits(f));
}

/**
  * 将float转为高字节在前,低字节在后的byte数组
  */
public static byte[] toHH(float f) {
  return toHH(Float.floatToRawIntBits(f));
}

/**
  * 将String转为byte数组
  */
public static byte[] stringToBytes(String s, int length) {
  while (s.getBytes().length < length) {
    s += " ";
  }
  return s.getBytes();
}


/**
  * 将字节数组转换为String
  * @param b byte[]
  * @return String
  */
public static String bytesToString(byte[] b) {
  StringBuffer result = new StringBuffer("");
  int length = b.length;
  for (int i=0; i<length; i++) {
    result.append((char)(b & 0xff));
  }
  return result.toString();
}

/**
  * 将字符串转换为byte数组
  * @param s String
  * @return byte[]
  */
public static byte[] stringToBytes(String s) {
  return s.getBytes();
}

/**
  * 将高字节数组转换为int
  * @param b byte[]
  * @return int
  */
public static int hBytesToInt(byte[] b) {
  int s = 0;
  for (int i = 0; i < 3; i++) {
    if (b >= 0) {
    s = s + b;
    } else {
    s = s + 256 + b;
    }
    s = s * 256;
  }
  if (b[3] >= 0) {
    s = s + b[3];
  } else {
    s = s + 256 + b[3];
  }
  return s;
}

/**
  * 将低字节数组转换为int
  * @param b byte[]
  * @return int
  */
public static int lBytesToInt(byte[] b) {
  int s = 0;
  for (int i = 0; i < 3; i++) {
    if (b[3-i] >= 0) {
    s = s + b[3-i];
    } else {
    s = s + 256 + b[3-i];
    }
    s = s * 256;
  }
  if (b[0] >= 0) {
    s = s + b[0];
  } else {
    s = s + 256 + b[0];
  }
  return s;
}


/**
  * 高字节数组到short的转换
  * @param b byte[]
  * @return short
  */
public static short hBytesToShort(byte[] b) {
  int s = 0;
  if (b[0] >= 0) {
    s = s + b[0];
    } else {
    s = s + 256 + b[0];
    }
    s = s * 256;
  if (b[1] >= 0) {
    s = s + b[1];
  } else {
    s = s + 256 + b[1];
  }
  short result = (short)s;
  return result;
}

/**
  * 低字节数组到short的转换
  * @param b byte[]
  * @return short
  */
public static short lBytesToShort(byte[] b) {
  int s = 0;
  if (b[1] >= 0) {
    s = s + b[1];
    } else {
    s = s + 256 + b[1];
    }
    s = s * 256;
  if (b[0] >= 0) {
    s = s + b[0];
  } else {
    s = s + 256 + b[0];
  }
  short result = (short)s;
  return result;
}

/**
  * 高字节数组转换为float
  * @param b byte[]
  * @return float
  */
public static float hBytesToFloat(byte[] b) {
  int i = 0;
  Float F = new Float(0.0);
  i = ((((b[0]&0xff)<<8 | (b[1]&0xff))<<8) | (b[2]&0xff))<<8 | (b[3]&0xff);
  return F.intBitsToFloat(i);
}

/**
  * 低字节数组转换为float
  * @param b byte[]
  * @return float
  */
public static float lBytesToFloat(byte[] b) {
  int i = 0;
  Float F = new Float(0.0);
  i = ((((b[3]&0xff)<<8 | (b[2]&0xff))<<8) | (b[1]&0xff))<<8 | (b[0]&0xff);
  return F.intBitsToFloat(i);
}

/**
  * 将byte数组中的元素倒序排列
  */
public static byte[] bytesReverseOrder(byte[] b) {
  int length = b.length;
  byte[] result = new byte[length];
  for(int i=0; i<length; i++) {
    result[length-i-1] = b;
  }
  return result;
}

/**
  * 打印byte数组
  */
public static void printBytes(byte[] bb) {
  int length = bb.length;
  for (int i=0; i<length; i++) {
    System.out.print(bb + " ");
  }
  System.out.println("");
}

public static void logBytes(byte[] bb) {
  int length = bb.length;
  String ut = "";
  for (int i=0; i<length; i++) {
    ut = out + bb + " ";
  }

}

/**
  * 将int类型的值转换为字节序颠倒过来对应的int值
  * @param i int
  * @return int
  */
public static int reverseInt(int i) {
  int result = FormatTransfer.hBytesToInt(FormatTransfer.toLH(i));
  return result;
}

/**
  * 将short类型的值转换为字节序颠倒过来对应的short值
  * @param s short
  * @return short
  */
public static short reverseShort(short s) {
  short result = FormatTransfer.hBytesToShort(FormatTransfer.toLH(s));
  return result;
}

/**
  * 将float类型的值转换为字节序颠倒过来对应的float值
  * @param f float
  * @return float
  */
public static float reverseFloat(float f) {
  float result = FormatTransfer.hBytesToFloat(FormatTransfer.toLH(f));
  return result;
}

}

 

程序间的通信,说到底便是发送和接收数据流。我们一般把字节(byte)看作是数据的最小单位。当然,其实一个字节中还包含8位(即bit位)。32位的处理器中“字长”为32个bit,也就是4个byte。在这样的CPU中,总是以4字节对齐的方式来读取或写入内存,那么同样这4个字节的数据是以什么顺序保存在内存中的呢? 这就是字节序的问题。

 

    一、字节序

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

    下面罗列常见的数据类型及其长度对照表:

            Wtypes.h 中的非托管类型  非托管C 语言类型     .net托管类名           长度
            HANDLE                        void*                   System.IntPtr        32 bit位
            BYTE                            unsigned char       System.Byte          8   bit位(每个字节Byte=2的4次方 * 2的4次方, 即0x6a 表示。)
            SHORT                         short                    System.Int16        16 bit位(也就是4Byte,即平常说的4字节。)
            WORD                          unsigned short      System.UInt16       16 位
            INT                               int                       System.Int32        32 位
            UINT                             unsigned int         System.UInt32       32 位
            LONG                            long                    System.Int32          32 位
            BOOL                            long                    System.Int32          32 位
            DWORD                        unsigned long       System.UInt32        32 位
            ULONG                          unsigned long      System.UInt32        32 位
            CHAR                            char                    System.Byte           8 位。 
            FLOAT                           Float                   System.Single         32 位
            DOUBLE                        Double                 System.Double        64 bit位(也就是8Byte,即平常说的8字节)

 

注意:

    是基于linux的程序的话,c/c++数据类型为long、long double和pointer时,其在32位和64位长度是不同的。如:long 在 x64和amd64下分别对应的4Byte和8Byte。
       如果是windows的程序的话,c++数据类型只有pointer在32位和64位长度是不同的。

 

    二、字节序分类

    有三种:Big-Endian、Little-Endian以及Middle-Endian。常见的主要是Big-Endian和Little-Endian,中文叫高字节序和低字节序(或者是大字节序和小字节序)。引用标准的Big-Endian和Little-Endian的定义如下:
    a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
    b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

    同时引进主机字节序和网络字节序概念。网络字节序肯定是Big-Endian高字节序,而主机字节序基本上是Little-Endian低字节序,但是主机字节序跟处理器即CPU类型有关,CISC架构的CPU如:X86(包括大部分的intel、AMD等PC处理器)是低字节序,而在嵌入式广泛应用的RISC架构的CPU如:ARM, PowerPC, Alpha, SPARC V9, MIPS等是高字节序的。

 

    三、字节序说明

    以c中定义一个16进制的变量为例:unsigned int value = 0x6a7b8c9d,根据上述类型对照表得知,c的unsigned int的value变量为32bit即4个byte。这里的value也等价于:

        unsigned char buf[4]= { 0x6a,0x7b,0x8c,0x9d}://语法有问题,就是相当于这么个初始化

    现在分别按转高字节序和低字节序解释下,加入这个buf变量的内存初始地址是a:
   a) Little-Endian: 低地址存放低位 :
 (高地址)--------------------------
   a+3-----  buf[3] (0x6a) -- 高位(相当于10进制的千位)
   a+2-----  buf[2] (0x7b)
   a+1-----  buf[1] (0x8c)
   a--------  buf[0] (0x9d) -- 低位(相当于10进制的个位)
(低地址)--------------------------

 

    b) Big-Endian: 低地址存放高位:
 (高地址)--------------------------
   a+3-----  buf[3] (0x9d) -- 高位(相当于10进制的千位)
   a+2-----  buf[2] (0x8c)
   a+1-----  buf[1] (0x7b)
   a--------  buf[0] (0x6a) -- 低位(相当于10进制的个位)
(低地址)--------------------------  

     一般的处理器高地址对应的是栈底,低地址对应的是栈顶。

 

    四、高/低字节序转换

     如果需要在不同的操作系统,或者是基于不同的语言,如c/c++和c#之间不同主机字节序之的网络通信,就需要考虑如何转换字节序。
     如果不转换可能遇到问题,这里举个C的客户端和服务器端通信的例子

     客户端(低字节序的主机,如intel 奔腾系列处理器)定义:  short x=1   为2个byte,在内存中为[1][0](低地址在前面),发送给服务器端。这服务器端(高字节序的主机,如嵌入式的ARM处理器)接收到的为:[1][0](低地址在前面),此时按照高字节序的"低地址存放高位"原则解析的x=256。于实际情况不符。

     所以针对这样的情况,编写跨平台或者是跨语言的程序是需要考虑字节序的转换。一般数据发送出去之前需要将主机字节序Little-Endian转换为网络字节序Big-Endian,接收之前需要将Big-Endian转为Little-Endian,下面介绍小.net和C/C++常见方法。

    4.1、 .net中:
主机字节序到网络字节序:short/int/long IPAddress.HostToNetworkOrder(short/int/long)
网络字节序到主机字节序:short/int/long IPAddress.NetworkToHostOrder(short/int/long)

    4.2 、C/C++根据类型的不同有如下方法:

        ntohs =net to host short int 16位
        htons=host to net short int 16位
        ntohl =net to host long int 32位
        htonl=host to net long int 32位

       简单的说明其中一个方法吧。

   将一个无符号短整形数从网络字节顺序转换为主机字节顺序。
   #include <winsock.h>
   u_short PASCAL FAR ntohs( u_short netshort);
      netshort:一个以网络字节顺序表达的16位数。
   注释:
       本函数将一个16位数由网络字节顺序转换为主机字节顺序。
   返回值:
       ntohs()返回一个以主机字节顺序表达的数。

 

   

    五、实际转换过程需要注意点及问题集。

    5.1:C++中的unsigned char类型等同于C#的什么类型?
    答:C#中的char是16bits的Unicode字符,而一般C++中的字符则是8位的,所以C++中的“unsigned   char”在C#中要么转换成char,要么使用Byte类型来代替,前者适用于存放字符型的unsigned   char,后者适用于整数型的unsigned   char。具体的程序,具体的方法。  比如:c++中申明变量,unsigned  char  para=0x4a(表示十六进制=2的4次方 ×2的4次方,即8位。 uchar的的范围为0-0xff,即0-255);unsigned  char  para[4] = 0x6789abcd,表示32位。

   5.2:为什么在网络编程中,即需要考虑字节序的问题时。对于double、float以及字符串等数据类型不需要考虑主机序列和网络序列之间的转换?
   答:至于float和double,与CPU无关。一般编译器是按照IEEE标准解释的,即把float/double看作4/8个字符的数组进行解释。因此,只要编译器是支持IEEE浮点标准的,就不需要考虑字节顺序。

   5.3: BinaryWriter和BinaryReader
    BinaryReader和BinaryWriter使用小字节序(即低字节序)读写数据。
    如例子:
    var stream = new MemoryStream(new byte[] { 4, 1, 0, 0 }); //相当于申请了byte[4],而每个byte相当于256进制
    var reader = new BinaryReader(stream);
    int i = reader.ReadInt32(); // i == 260
    //因为BinaryReader是按照低字节序读取的,所有i=4+256×1=260;

   5.4、BitConverter和ASCIIEncoding.ASCII.GetBytes
   BitConverter主要是用于.net中byte[]和其他类型的转换,不涉及字符串数据类型。网络通信会经常用到。
   ASCIIEncoding.ASCII通常用于字符串和byte[]之间的转换。

 

   5.x: 关于“同样4个字节的数据,我们可以把它看作是1个32位整数、2个Unicode、或者字符4个ASCII字符。”引申:
   unicode和utf-8之间最大的区别就是在存储上。unicode是宽字符存储(字符都是2个字节或4个字节来存储),而utf-8是多字节存

储,字符的个数是不确定的(比如英文字符是1个字节表示,汉字可以是2个到6个来表示),其字符的首字节的前几位表明了它的字节

个数。比如某个3字节汉字的uft-8编码(二进制)如下:  
  1110xxxx   10xxxxxx   10xxxxxx  
  首字节中1的个数为3表明该汉字用3个字节来表示。
 
  utf-16固定使用两个字节表示一个字符,平常所说的unicode编码就是utf-16的。

  ascii字符在utf-8里面还是一样的,一个字节表示,超出ascii字符范围的,就用多字节表示,字节的数目由第一字节确定,最多6

字节。如下:  
  utf-8:  
  1字节:0XXXXXXX(ascii)  
  2字节:110XXXXX   10XXXXXX  
  3字节:1110XXXX   10XXXXXX   10XXXXXX  
  4字节:11110XXX   10XXXXXX   10XXXXXX   10XXXXXX  
  5字节:。。。  
  utf-16:所有:XXXXXXXX   XXXXXXXX  
  ascii:XXXXXXXX   00000000

 

   ASCII码是用十六进制表示,也就是说它是两个字节byte。如:ASCII码为31(即0x31)对应的字符"1";ASCII码为32(即0x31)对

应的字符"2".

 

你可能感兴趣的:(网络字节序)