16.1.4 网络字节顺序
不同机器架构使用不同的字节顺序存储数据,如基于Intel处理器的机器与Macintosh(Motorola)机器存储数据的字节顺序是相反的。Intel采用的字节顺序称为"小头方式",即低字节在前,高字节在后的方式,而标准的网络顺序是"大头方式",即高字节在前,低字节在后的方式。表示如下:
将0x12345678写入到以0x0000开始的内存中,则结果为
- 大头方式 小头方式
- 0x0000 0x12 0x34
- 0x0001 0x34 0x12
- 0x0002 0x56 0x78
- 0x0003 0x78 0x56
其中,0x12、0x34、0x56、0x78各是一个字节,组合字节顺序时,是以字Word为单位的,每个字包括两个字节,低字节和高字节。小头方式就是低字节在前,大头方式就是高字节在前。字之间是按照正常顺序。
一般情况下,用户不需要处理在网络上发送和接收数据的字节顺序的转换,但是在下列情况下,需要用户手动转换字节顺序。
用户传输的信息需要网络解释,这与发送到其他机器的数据不一样。如用户传输端口和地址时,必须由网络理解。
当与之通信的服务器应用程序不是MFC应用程序时,如果通信的两台机器使用的字节顺序不同,则需要调用字节转换。
而下列情况下,不需要用户手动调用字节转换。
两台机器使用相同的字节顺序,并且两端约定不进行字节交换。
与之通信的服务器是MFC应用程序。
用户有与之通信的服务器的源代码,因此,可以显式地说明是否转换字节顺序。
可以将服务器转换成MFC程序。
在后面会介绍到MFC的CAsyncSocket类,如果使用此类,用户必须自己管理需要的字节顺序转换。Windows Socket标准化"大头方式"字节顺序模型,并提供与"小头方式"字节顺序的转换函数。而CSocket使用的CArchive类使用"小头方式"字节顺序,但是CArchive类处理了字节顺序转换的细节。通过在应用程序中使用标准的字节顺序,或使用Windows Sockets字节顺序转换函数,用户可以编写灵活的代码。
如果使用MFC Sockets编程(www.cppentry.com),即客户端和服务器端都使用MFC,则不需要关心字节顺序的细节。如果编写与非MFC应用程序进行通信的应用程序,如FTP服务器,则用户在将数据传入存档对象前,需要自己管理字节顺序转换。Windows Sockets提供了4个转换函数,ntohs()、ntohl()、htons()和htonl(),如表16-1所示。
表16-1 字节转换函数
|
函 数< xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> |
功 能 |
|
ntohs() |
将16位数从网络字节顺序转换成主机
字节顺序,即从“大头方式”转换成“小头方式” |
|
ntohl() |
将32位数从网络字节顺序转换成主机字
节顺序,即从“大头方式”转换成“小头方式” |
|
htons() |
将16位数从主机字节顺序转换成网络字
节顺序,即从“小头方式”转换成“大头方式” |
|
htonl() |
将32位数从主机字节顺序转换成网络字节顺
序,即从“小头方式”转换成“大头方式” |
下面以使用存档的CSocket对象的序列化函数为例,说明如何使用字节转换顺序。假定与编写的程序通信的是非MFC服务器应用程序,并且没有源代码。此时,非MFC服务器使用的是标准网络字节顺序"大头方式"。编写的MFC客户端应用程序通过CSocket对象使用CArchive对象,而CArchive使用"小头方式"字节顺序。协议通信包为:
- struct MyPack // 自定义数据包
- {
- long Number; // 流水号
- unsigned short Command; // 命令标识
- short ParamA; // 参数A
- short ParamB; // 参数B
- };
在MFC中,其定义为:
- struct MyPack // 自定义数据包
- {
- long m_lNumber; // 流水号
- short m_nCommand; // 命令标识
- short m_nParamA; // 参数A
- short m_lParamB; // 参数B
- void Serialize( CArchive& ar ); // 序列化函数
- };
其中,Serialize()是序列化函数,其定义为:
- void MyPack::Serialize(CArchive& ar) // 自
定义数据包类的序列化函数 - {
- if (ar.IsStoring())
// 如果是存储对象 - {
- ar << (DWORD)htonl(m_lMagicNumber); // 存储
流水号为DWORD - ar << (WORD)htons(m_nCommand); // 存储
命令标识为WORD - ar << (WORD)htons(m_nParamA); // 存储
参数A为WORD - ar << (WORD)htonl(m_lParamB);
// 存储参数B为WORD - }
- else
// 如果是提取对象 - {
- WORD w; // 定义WORD变量
- DWORD dw; // 定义DWORD变量
- ar >> dw; // 提取DWORD值
- m_lMagicNumber = ntohl((long)dw); // 将提
取的DWORD值转换为流水号 - ar >> w ; // 提取WORD值
- m_nCommand = ntohs((short)w); // 将提取
的WORD值转换为命令标识 - ar >> w; // 提取WORD值
- m_nParamA = ntohs((short)w);
// 将提取的WORD值转换为参数A - ar >> w;
// 提取WORD值 - m_lParamB = ntohl((short)w);
// 将提取的WORD值转换为参数A - }
- }
在本例中,非MFC服务器应用程序使用的字节顺序与客户端MFC应用程序CArchive字节顺序不同,因此,使用了字节顺序转换函数对数据进行了转换。当序列化函数存储数据时,首先将数据从主机顺序转换成网络顺序,然后将其存储;当序列化函数提取数据时,首先将数据从网络顺序转换成主机顺序,然后赋值给变量。