深入研究套接字理论之前,先讨论网络编程令人不快的一个问题,即字节顺序(byte ordering)。提到这一主题时所认识的几乎每一个人都会对此发出抱怨。
早先计算机只有少量内存和很小的数据总线。一般情况下,计算机中数据总线的大小称为字长(word size)。例如,最早有些计算机的总线大小是4比特,因此它们的字长是4比特。
注意:4比特数据组成的组称为“半字节(nibble)”,它适用于整个比特(bit)和字节(byte)主题。
显而易见,4比特CPU的处理能力也就这么多,因此发明了更大的使用8比特数据的机器。这些8比特机器暂时成为标准,而8比特数据长度也成为标准的原子数据结构,这就意味着所能存储的最小的单一数据是8比特,8比特也称为一个字节。
并不想要深入探讨二进制数学,只是简短地介绍一下需要了解的内容。
在数制中,最靠右边的数字具有最小的权(假设数字是从左往右书写)。在十进制中,最右边的一位是1,接着往左的一位是10,再接着往左的一位是100等。二进制与此相同,只是这些列分别是1、2和4,每一个值都是前一个值的两倍,如图2.1所示。此图逐列对照比较以10为基数与以2为基数的数的数字排列。
![](http://book.csdn.net/BookFiles/205/img/image016.gif)
图2.1 以10为基数与以2为基数的数的数字排列
因此,当将数据长度从8比特增加到16比特时,实质上就已经假定了新增加的8比特位于原来的8比特的左边,原来的8比特表示数的低位,新的8比特表示数的高位,如 图2.2所示。
![](http://book.csdn.net/BookFiles/205/img/image017.gif)
图2.2 big-endian存储格式
遗憾的是,事情并不是那么简单。当计算机开始转变到16位时,人们意识到有很多代码仍然运行在8位系统上,芯片设计人员认为使16位处理器同时也能运行8位代码是一个非常好的设想。毕竟,向后兼容是一件非常好的事情。
由于计算机体系结构的局限性,要求16位处理器将它们的内存界限统一调整到16位,并存储在16位内存区域中,即使数据只有8位长度也必须如此。因此,假如将一个8位数据存入内存中(当然,没有智能字节处理功能),编译器则要将它转换成16位数,并且要对齐到16位边界长度而后存储起来。回顾图2.2,可以帮助描绘这一概念。在此图中,数据存储在地址0,但是实际数据放置在地址1,地址0所放置的值是0。
如果稍后想要通过指针检索这个8位数值,那么想象一下,会发生什么事呢?我们装载地址0,将它当作一个字节,并加载它,但数据实际上在字节1。
当然处理器可以自动转换地址,但是这会使处理器复杂得多,而且由于处理器变复杂,所以速度更慢,费用更昂贵。
因此,大多数芯片制造商采取的解决方案是交换字节顺序,如图2.3所示。这样,无论是8位程序还是16位程序,都知道它们的数据存放在哪里。将图2.3与图2.2进行比较,了解交换字节顺序后的数。将图2.2中数据的存储格式称作“big-endian(大头顺序)”,将图2.3中数据的存储格式称作“little-endian(小头顺序)”。(多字节数在存储或传输时,big-endian格式是指先存储或发送高位字节,即一个字中的若干字节,权值最高的字节在最左边;little-endian格式是指先存储或发送低位字节,即一个字中的若干字节,权值最高的字节在最右边)。
![](http://book.csdn.net/BookFiles/205/img/image018.gif)
![](http://book.csdn.net/BookFiles/205/img/image019.gif)
图2.3 little-endian存储格式
遗憾的是,这就产生了非常混乱的局面,因为有些芯片采用little-endian格式,而有些芯片采用big-endian格式。当这些计算机彼此之间试图用大于一个字节的数据进行通信时,就产生了问题。
注意:big-endian和little-endian实际上出自乔纳森·斯威夫特的经典名著《格利佛游记》(它并不是一个少儿故事,实际上是一个严厉的时事评论,不过是以前的报道)。在这一著作中,两个部落不断地发生冲突,争论煮老的鸡蛋应该先吃大头还是小头,这两头分别称为little-endian和big-endian。你看我们每天都可以学到新知识。
显而易见,这对联网来说是一个很大的问题,因为网络上的数据必须有一个标准的字节顺序。因此,当最初创建因特网时,创建者们决定网络字节顺序采用big-endian格式。每一个包首部中的字节顺序都应该采用big-endian格式,即固有的数学顺序。协议首部之外的数据组织格式则最终取决于自己的意愿以及应用层协议的设计方式,但是,出于一致性方面的考虑,通常还是建议数据采用big-endian格式。
现在面临的重要问题是:如何把数据从自己的主机字节顺序(哪些采用big-endian格式,或哪些不采用big-endian格式,则取决于系统)转换为网络字节顺序呢?Socket API可以很好地解决这一问题,它包括下面4个函数可以完成这一任务:
// 从主机字节顺序转换为网络字节顺序(长整型):
unsigned long htonl( unsinged long );
// 从网络字节顺序转换为主机字节顺序(长整型):
unsigned long ntohl( unsigned long );
// 从主机字节顺序转换为网络字节顺序(短整型):
unsigned short htons( unsigned short );
// 从网络字节顺序转换为主机字节顺序(短整型):
unsigned short ntohs( unsigned short );