网络发送数据字节序的处理

其实,不止是ip和port需要转换成网络字节序,如果发送端和接收端字节序不一样,有些情况下也要转换。参考unix网络编程卷1的5.18节,下面贴几张图



以下文字复制自论大小端


对于跨平台的程序或者所用数据牵扯到不同平台的程序(例如网络编程),大小端字节序是个值得考虑的事情。本文主要讨论一下网络编程方面的大小端问题。(by peakflys)
   先来说一下几个定义:
   a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。(逻辑上的低低高高)
   b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。(像数据流一样填充)
   c) 网络字节序:TCP/IP各层协议将字节序定义为Big-Endian,因此TCP/IP协议中使用的字节序通常称之为网络字节序。

   因为字节序往往和具体CPU架构有关,所以 如果你知道你的程序主要用户群是什么平台,为了方便或者效率,你可以除了socket端口等需要在主机字节序和网络字节序之间转换外,其余数据的传递直接无视。例如 现在很多端游 都是如此。因为现在大多数人使用的计算机都是X86体系结构的CPU+Windows操作系统,这部分用户基本就是主流玩家,其他平台的玩家,除非获得的回报率足够多,否则没必要花费太多时间关注。
   先来说一下,常见的CPU架构的字节序吧:
      Big Endian : PowerPC、IBM、Sun
      Little Endian : x86、DEC
      ARM的大小端是可选的。
   最近随着移动终端(大多为ARM处理器)和移动互联网的爆发式发展,以后的游戏平台就不得不考虑一下大小端问题了。
   大小端问题主要涉及的是非单字节非字符串外的其余数据的表示和传递,如short型、int型等。判断主机大小端的方法有很多,常见的是联合体判断法,代码如下:

01.bool isBigEndian()  
02.{  
03.    union
04.    {  
05.        int a;  
06.        char b;  
07.    }num;  
08.    num.a = 0x1234;  
09.    return ( num.b == 0x12 )   
14.}
出于效率考虑,我们有理由也完全应该 把大小端的处理放在客户端,在客户端socket过来时把服务器主机的大小端通知给客户端,这样服务器就不需要改动,直接传递数据就行,这时候可以在客户端代码中封装几个宏,在客户端在收到数据后,根据那些宏来判断是否转换以及得出转换后的数值。大小端转换最有效也是最常见的方法就是移位法:
#define __SWP16(A)   (( ((uint16)(A) & 0xff00) >> 8)    | \  
(( (uint16)(A) & 0x00ff) << 8))  

#define __SWP32(A)   ((( (uint32)(A) & 0xff000000) >> 24) | \  
(( (uint32)(A) & 0x00ff0000) >> 8)   | \  
(( (uint32)(A) & 0x0000ff00) << 8)   | \  
(( (uint32)(A) & 0x000000ff) << 24)) 
聊了那么多,可能很多人要问 为什么 主机的字节序不统一呢? 这是因为 各个CPU厂商出于不同的逻辑考量,换句话说 大端和小端有其各自的优势。我们知道计算机正常的内存增长方式是从低到高(当然栈不是),取数据方式是从基址根据偏移找到他们的位置,从他们的存储方式可以看出,大端存储因为第一个字节就是高位,从而很容易知道它是正数还是负数,对于一些数值判断会很迅速。而小端存储 第一个字节是它的低位,符号位在最后一个字节,这样在做数值四则运算时从低位每次取出相应字节运算,最后直到高位,并且最终把符号位刷新,这样的运算方式会更高效,也更符合我们手算的方式。当然这些都是自己的理解,如有不对,还望指正。

以下内容为自己总结:(协议部分都是大端,应用可以自己定 

1、编程时,ip和port需要转换成网络字节序,这是网络协议的规定,注意,ip和port并不是到达应用层数据的一部分,而是网络层和传输层头部的一部分,是属于协议的一部分协议;

2、我们发送的数据是真正从发送端的应用层传输到接收端的应用层的数据,如果发送端和接收端字节序不一样,那么就要考虑字节转换。其根本是:网络传输不一定要统一大端,小端也可以,只要保证2端一致就行了。因为服务器负担大,这些工作就由客户端去做,可以有以下两种方式:1)编程之前,用文档约束好,服务器采用的是大端或者小端,然后客户端根据自己的大小端情况去转换成与服务器一样的字节序再发送和接受;2)客户端连接服务器后,服务器先发送一个表示大小端的1字节的数据到客户端,客户端接收后得知服务器的大小端,以后发送和接收数据时作大小端的调整再发送和接收。


参考:网络上的数据流是字节流,对于一个多字节数值,在进行网络传输的时候,先传递哪个字节?也就是说,当接收端收到第一个字节的时候,它是将这个字节作为高位还是低位来处理呢? 
网络字节序定义:收到的第一个字节被当作高位看待,这就要求发送端发送的第一个字节应当是高位。而在发送端发送数据时,发送的第一个字节是该数字在内存中起始地址对应的字节。可见多字节数值在发送前,在内存中数值应该以大端法存放。 


以下内容拷贝自再议字节序-------TCP/IP网络传输相关字节序


一、
在网络编程时,并不是什么时候都要考虑字节序问题。那么什么时候需要考虑呢?
实际上如果是应用层的数据,即对TCP/IP来说是透明的数据,不用考虑字节序的问题。因为发出去是什么样子,接收到就还是什么样子,接收端收到的顺序是和发送端一致的
TCP/IP协议本身处理数据的时候都是按照网络字节序来处理的。当一个数据是应用层和TCP/IP都需要关心的时候,如网络函数的数字类型参数,需要考虑这个数据的字节序(一般而言就是IP地址和端口号)。

例如我指定了一个端口号:
unsigned short port = 0x0012  (十进制18)
把这个端口号传个TCP/IP建立一个socket连接。
sockaddr_in addr;
 addr.sin_family = AF_INET; //使用互联网际协议,即IP协议
 addr.sin_port = port;
addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
因为网络字节序是big endian,即低地址存放的是数值的高位,所以TCP/IP实际上把这个port解释为0x1200(十进制4608)。本来打算是要在端口18建立连接的,但TCP/IP协议栈却在端口4608建立了连接。故先用htons(port)把端口号转换成网络字节序,这样就可避免这种问题了。

二、
字节序问题归根结底就是因为它们的编码单元是多个字节,而且这多个字节的顺序在不同机器上可能不一样(大头或小头字节序)。
如四个字节才能表示一个int整形,utf-16编码最小编码单元是两个字节。

对于TCP/IP而言,发送端的字节以什么顺序排列的,接受端就是受到完全一样顺序排列的字节。
接收方接收到数据后,但是需要知道字节序才能正确地解析这些数据。

假设现在要在使用不同字节顺序的机器之间传输和交换数据,那该怎么办呢?
有两种方法,一种是全部转换成文本来传输,当然也要以两个主机有相同的字符集为基础;另一种是双方都按照某一方的字节顺序来传输(这时就有一个不同字节顺序之间的相互转换问题)。见Unix网络编程第一卷P118数据格式。

Socket编程中经常采用第二种方法。整个传输过程如下:发送端将本机的数据转换成网络的字节顺序(调用API函数htonl或htons),然后发送;接收端收到网络数据后,先将数据转换成本机的字节顺序(调用API函数ntohl或ntohs)。

你可能感兴趣的:(网络发送数据字节序的处理)