字节顺序以及在网络传输前要做的转换

字节顺序 Big-Endian&Little-Endian

字节顺序 Big-Endian&Little-Endian
2007-06-19 14:42

1,什么是字节顺序

指的就是数据在内存里存储的顺序,通常我们默认一个存储单元为1Byte,一个逻辑数据比如Float是4个Byte,它在内存理存储的顺序可以是高位在前,也可以是低位在前.这样就有区分了.

2,有些什么顺序:

Big-Endian(MSB),高位在低地址

Little-Endian(LSB),低位在低地址

Endian指的是当物理上的最小单元比逻辑上的最小单元小时,逻辑到物理的单元排布关系

3,为什么要有字节顺序?

因为程序需要与外界通讯,传送数据,这里的外界包括其他机器和其他语言写的程序.比如x86下c++程序与linux底下的程序通讯,比如c++和java的程序通讯.都会涉及到字节存放顺序的问题.

3,哪些会是Big-Endian,哪些会是Little-Endian

网络协议都是Big-Endian的,Java编译的都是Big-Endian的

Motorola的PowerPC是Big-Endian的

x86系列则采用Little-Endian方式存储数据。

ARM同时支持 big和little,实际应用中通常使用Little-Endian

4,如何判断我现在的系统里采用什么字节存储顺序?

/*用于判断存储格式是little endian还是big ednian的C代码*/
#include "stdafx.h"

using namespace std;

union
{
long   Long;
char   Char[sizeof(long)];
} u;

void   main()
{
   u.Long=1;
   if(u.Char[0]==1)
   {
    printf("Little Endian!"n");

   }
   else if(u.Char[sizeof(long)-1]==1)
   {
    printf("Big Endian!"n");
   }
   else
   {
    printf("Unknown Addressing!"n");
   }

   printf("Now,Let's look at every byte in the memory!"n");
  
   for(int i=0;i<sizeof(long);++i)
   {
    printf("[%x]=%x"n",&u.Char[i],u.Char[i]);
   }

   getchar();
   return;
}

5,如何转换字节顺序,Big-Endian-->Little-Endian or Little Endian->Big Endian.

#include <stdio.h>
const unsigned char SIZE_OF_UNSIGNEDINT = sizeof(unsigned int);
const unsigned char SIZE_OF_UNSIGNEDCHAR = sizeof(unsigned char);

void put_32(unsigned char * cmd, unsigned int data)
{
    int i;
    for (i = SIZE_OF_UNSIGNEDINT - 1 ; i >= 0 ; --i)
    {
     cmd[i] = data % 256 ;
      // 或者可以:
      // cmd[i] = data & 0xFF;
     data = data >> 8 ;
    }
}

unsigned int get_32(unsigned char * cmd)
{
    unsigned int ret;
    int i;
    for (ret = 0 , i = SIZE_OF_UNSIGNEDINT - 1 ; i >= 0 ; -- i)
    {
     ret = ret << 8 ;
     ret |= cmd[i];  
    }  
    return ret;
}

int main( void)
{
   unsigned char cmd[SIZE_OF_UNSIGNEDINT];
   unsigned int data, ret;
   unsigned char * p;
   int i;
   data = 0x12345678 ;
   printf( "data = %x"n " , data);
    // 以字节为单位打印出数据
    p = (unsigned char *)( & data);
    for(i = 0 ; i < SIZE_OF_UNSIGNEDINT; ++ i)
    {
   printf( " %x " , * p ++);
    }
    printf( " "n ");
    //以相反的顺序存放到cmd之中
    put_32(cmd, data);
    for (i = 0 ; i < SIZE_OF_UNSIGNEDINT; ++ i)
    {
    printf( " cmd[%d] = %x"n " , i, cmd[i]);
    }
    // 再以相反的顺序保存数据到ret中
    // 保存之后的ret数值应该与data相同
    ret = get_32(cmd);
    printf( " ret = %x"n " , ret);
    p = (unsigned char *)( & ret);
    for(i = 0 ; i < SIZE_OF_UNSIGNEDINT; ++ i)
    {
    printf( " %x " , * p ++);
    }
    printf( " "n ");
    getchar();
    return 0 ;
}

 

今天碰一个关于字节顺序的问题,虽然看起来很简单,但一直都没怎么完全明白这个东西,索性就找了下资料,把它弄清楚.

    因为现行的计算机都是以八位一个字节为存储单位,那么一个16位的整数,也就是C语言中的short,在内存中可能有两种存储顺序big-endian和 litte-endian.考虑一个short整数0x3132(0x32是低位,0x31是高位),把它赋值给一个short变量,那么它在内存中的存 储可能有如下两种情况:
大端字节(Big-endian):
----------------->>>>>>>> 内存地址增大方向
short变量地址
       0x1000                  0x1001
_____________________________
|                            |
|         0x31             |       0x32
|________________ | ________________
高位字节在低位字节的前面,也就是高位在内存地址低的一端.可以这样记住(大端->高位->在前->正常的逻辑顺序)
 
小端字节(little-endian):
----------------->>>>>>>> 内存地址增大方向
short变量地址
       0x1000                  0x1001
_____________________________
|                           |
|         0x32             |       0x31
|________________ | ________________
低位字节在高位字节的前面,也就是低位在内存地址低的一端.可以这样记住(小端->低位->在前->与正常逻辑顺序相反)
 
可以做个实验
在windows上下如下程序
#include <stdio.h>
#include <assert.h>
 
void main( void )
{
        short test;
        FILE* fp;
        
        test = 0x3132;  //(31ASIIC 码的’1’,32ASIIC码的’2’)
        if ((fp = fopen ("c://test.txt", "wb")) == NULL)
              assert(0);
        fwrite(&test, sizeof(short), 1, fp);
        fclose(fp);
}
    然后在C盘下打开test.txt文件,可以看见内容是21,而test等于0x3132,可以明显的看出来x86的字节顺序是低位在前.如果我们把这段 同样的代码放到(big-endian)的机器上执行,那么打出来的文件就是12.这在本机中使用是没有问题的.但当你把这个文件从一个big- endian机器复制到一个little-endian机器上时就出现问题了.
    如上述例子,我们在big-endian的机器上创建了这个test文件,把其复制到little-endian的机器上再用fread读到一个 short里面,我们得到的就不再是0x3132而是0x3231了,这样读到的数据就是错误的,所以在两个字节顺序不一样的机器上传输数据时需要特别小 心字节顺序,理解了字节顺序在可以帮助我们写出移植行更高的代码.
正因为有字节顺序的差别,所以在网络传输的时候定义了所有字节顺序相关的数据都使用big-endian,BSD的代码中定义了四个宏来处理:
#define ntohs(n)      // 网络字节顺序到主机字节顺序 n代表net, h代表host, s代表short
#define htons(n)      // 主机字节顺序到网络字节顺序 n代表net, h代表host, s代表short
#define ntohl(n)       // 网络字节顺序到主机字节顺序 n代表net, h代表host, s代表 long
#define htonl(n)       // 主机字节顺序到网络字节顺序 n代表net, h代表host, s代表 long
 
举例说明下这其中一个宏的实现:
 #define sw16(x) /
    (( short )( /
        ((( short )(x) & ( short )0x00ffU) << 8) | /
        ((( short )(x) & ( short )0xff00U) >> 8) ))
这里实现的是一个交换两个字节顺序.其他几个宏类似.

我们改写一下上面的程序
#include <stdio.h>
#include <assert.h>

#define sw16(x) /
    ((short)( /
        (((short)(x) & (short)0x00ffU) << 8) | /
        (((short)(x) & (short)0xff00U) >> 8) ))

// 因为x86下面是低位在前,需要交换一下变成网络字节顺序
#define htons(x) sw16(x)
 
void main( void )
{
        short test;
        FILE* fp;
         
        test = htons(0x3132); //(31ASIIC 码的’1’,32ASIIC码的’2’)
        if ((fp = fopen ("c://test.txt", "wb")) == NULL)
              assert(0);
        fwrite(&test, sizeof(short), 1, fp);
        fclose(fp);
}
 
    如果在高字节在前的机器上,由于与网络字节顺序一致,所以我们什么都不干就可以了,只需要把#define htons(x) sw16(x)宏替换为 #define htons(x) (x).
    一开始我在理解这个问题时,总在想为什么其他数据不用交换字节顺序?比如说我们write一块buffer到文件,最后终于想明白了,因为都是unsigned char类型一个字节一个字节的写进去,这个顺序是固定的,不存在字节顺序的问题,够笨啊..


Big-Endian 最重要的字节在词的左端。 Little-Endian 最重要的字节在词的右端。 通常,您不必为在网络上发送和接收的数据的字节顺序转换担心,但在有些情况下,您必须转换字节顺序。

何时必须转换字节顺序

在下列情况中需要转换字节顺序:
  • 传递的信息需要由网络解释(与发送到另一台计算机的数据相反)。例如,可能传递端口和地址,这些信息
必须为网络理解。
  • 与之通信的服务器应用程序不是 MFC 应用程序(并且没有它的源代码)。假如两台计算机不共享相同的字节
排序,则需要字节顺序转换。

何时不必转换字节顺序

在下列情况下可以免去转换字节顺序的工作:
  • 两端的计算机可以同意不交换字节,并且这两台计算机使用相同的字节顺序。
  • 与之通信的服务器是 MFC 应用程序。
  • 有与之通信的服务器的源代码,因此可以明确地判定是否必须转换字节顺序。
  • 可以将服务器移植到 MFC。这样做相当轻易,所得到的通常是更小、更快的代码。
使用 CAsyncSocket 时,您必须自己治理任何必需的字节顺序转换。Windows Sockets 将“Big-Endian”字节顺 序模型标准化,并提供在该顺序和其他顺序之间转换的函数。然而,与 CSocket 一起使用的 CArchive 使用相 反的顺序(“Little-Endian”),但 CArchive 为您治理字节顺序转换的细节。通过在应用程序中使用这种标准 排序,或通过使用 Windows Sockets 字节顺序转换函数,可增强代码的可移植性。 最适合使用 MFC 套接字的情况是当编写通信的两端时:在两端都使用 MFC。假如正在编写将与非 MFC 应用程序 通信的应用程序(如 FTP 服务器),则在向存档对象传递数据前,您可能需要使用 Windows Sockets 转换例程 ntohs 、 ntohl 、 htons 和 htonl 自己治理字节交换。本文稍后将给出这些用于与非 MFC 应用程序通信的函数 示例。
注重 当通信的另一端不是 MFC 应用程序时,也必须避免将从 CObject 派生的 C++ 对象以流的形式输入存 档,因为接收端无法处理它们。请参见 Windows Sockets:使用带存档的套接字中的说明。 有关字节顺序的更多信息,请参见 Platform SDK 中的 Windows Sockets 规范。

字节顺序转换示例

下面的示例显示使用存档的 CSocket 对象的一个序列化函数。它还阐释了在 Windows Sockets API 中如何使用 字节顺序转换函数。 该示例显示这样的情形:您正在编写与非 MFC 服务器应用程序通信的客户程序,而您没有访问该服务器应用程序 源代码的权限。在这种情况下,必须假设非 MFC 服务器使用标准的网络字节顺序。相反,MFC 客户端应用程序对 CSocket 对象使用 CArchive 对象,而 CArchive 使用与网络标准相反的“Little-Endian”字节顺序。 假设要与之通信的非 MFC 服务器具有如下已建立的消息包协议: strUCt Message { long MagicNumber; unsigned short Command; short Param1; long Param2; };
   上述内容用 MFC 术语表示则为: struct Message { long m_lMagicNumber; short m_nCommand; short m_nParam1; long m_lParam2; void Serialize
   ( CArchive& ar ); }; 在 C++ 中, struct 和类在本质上是一回事。 Message 结构可以有成员函数,如以上声明的 Serialize 成员函数。 Serialize 成员函数可能为如下形式: void Message::Serialize(CArchive& ar)
  {
  if (ar.IsStoring())
  {
  ar < < (DWord)htonl(m_lMagicNumber);
  ar < < (WORD)htons(m_nCommand);
  ar < < (WORD)htons(m_nParam1);
  ar < < (DWORD)htonl(m_lParam2);
  }
  else
  {
  WORD w;
  DWORD dw;
  ar > > dw;
  m_lMagicNumber = ntohl((long)dw);
  ar > > w ;
  m_nCommand = ntohs((short)w);
  ar > > w;
  m_nParam1 = ntohs((short)w);
  ar > > dw;
  m_lParam2 = ntohl((long)dw);
  }
  }
  
该示例要求进行数据字节顺序转换,因为一端的非 MFC 服务器应用程序的字节排序与另一端在 MFC 客户端应用程序中使用的 CArchive 明显不匹配。该示例阐释了 Windows Sockets 提供的几个字节顺序转换函数。下表描述了这些函数。 Windows Sockets 字节顺序转换函数
ntohs 将 16 位数量从网络字节顺序转换为主机字节顺序(从 Big-Endian 转换为 Little-Endian)。 ntohl 将 32 位数量从网络字节顺序转换为主机字节顺序(从 Big-Endian 转换为 Little-Endian)。 htons 将 16 位数量从主机字节顺序转换为网络字节顺序(从 Little-Endian 转换为 Big-Endian)。 htonl 将 32 位数量从主机字节顺序转换为网络字节顺序(从 Little-Endian 转换为 Big-Endian)。 此示例的另一个要点是,当通信另一端的套接字应用程序为非 MFC 应用程序时,必须避免出现如下列语句的操作: ar pMsg; 这里的 pMsg 是指向从 CObject 类派生的 C++ 对象的指针。这将发送多余的与对象关联的 MFC 信息,而服务器并不理解这些信息,因为只有服务器是 MFC 应用程序时才理解。 有关更多信息,请参见:
  • Windows Sockets:使用 CAsyncSocket 类
  • Windows Sockets:使用带存档的套接字
  • Windows Sockets:背景知识
  • Windows Sockets:流式套接字
  • Windows Sockets:数据文报套接字
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值