Share via


Windows 套接字:字节排序

本文和两篇配套文章介绍了 Windows 套接字编程中的若干问题。 本文介绍字节排序。 文章中涉及的其他问题:Windows 套接字:阻止Windows 套接字:转换字符串

如果使用类 CAsyncSocket 或从其派生,则需要自行管理这些问题。 如果使用类 CSocket 或从其派生,则将由 MFC 管理这些问题。

字节排序

不同计算机体系结构有时使用不同字节顺序存储数据。 例如,基于 Intel 的计算机以 Macintosh (Motorola) 计算机的相反向顺序存储数据。 Intel 字节顺序(称为“little-Endian”)也与网络标准“big-Endian”顺序相反。 下表说明了这些术语。

Big-Endian 和 Little-Endian 字节顺序

字节排序 含义
Big-Endian 最高有效字节位于字的左端。
Little-Endian 最高有效字节位于字的右端。

通常,不必担心通过网络发送和接收的数据的字节顺序转换,但在某些情况下必须转换字节顺序。

必须转换字节顺序时

在以下情况下,需要转换字节顺序:

  • 在传递需要由网络进行解释的信息,而不是发送到另一台计算机的数据。 例如,可能会传递网络必须理解的端口和地址。

  • 与之通信的服务器应用程序不是 MFC 应用程序(并且没有其源代码)。 如果两台计算机不共享相同的字节排序,则此情况需要字节顺序转换。

不必转换字节顺序时

在以下情况下,可以避免转换字节顺序的工作:

  • 两端的计算机可以同意不交换字节,并且两台计算机使用相同的字节顺序。

  • 与之通信的服务器是 MFC 应用程序。

  • 具有与之通信的服务器的源代码,因此可以明确判断是否必须转换字节顺序。

  • 可以将服务器移植到 MFC。 这相当容易实现,结果通常是获得更小、更快的代码。

使用 CAsyncSocket 时,必须自行管理任何必要的字节顺序转换。 Windows 套接字会标准化“big-Endian”字节顺序模型,并提供用于在此顺序与其他顺序之间进行转换的函数。 然而,与 CSocket 一起使用的 CArchive 使用相反的(“little-Endian”)顺序,但 CArchive 会为你处理字节顺序转换的细节。 通过在应用程序中使用此标准排序或使用 Windows 套接字字节顺序转换函数,可以使代码更易于移植。

使用 MFC 套接字的理想案例是在编写通信的两端时:在两端使用 MFC。 如果要编写与非 MFC 应用程序(例如 FTP 服务器)通信的应用程序,则可能需要在将数据传递到存档对象之前,使用 Windows 套接字转换例程 ntohs、ntohl、htons 和 htonl 自行管理字节交换。 本文后面部分将介绍在与非 MFC 应用程序通信时使用的这些函数的示例。

注意

当通信的另一端不是 MFC 应用程序时,还必须避免将派生自 CObject 的 C++ 对象流式处理到存档中,因为接收方无法处理它们。 请参阅 Windows 套接字:对存档使用套接字中的说明。

有关字节顺序的详细信息,请参阅 Windows SDK 中提供的 Windows 套接字规范。

字节顺序转换示例

以下示例演示使用存档的 CSocket 对象的序列化函数。 它还演示如何使用 Windows 套接字 API 中的字节顺序转换函数。

此示例展示了一种情形:你在编写一个客户端,该客户端与你无权访问其源代码的非 MFC 服务器应用程序通信。 在此情形中,必须假设非 MFC 服务器使用标准网络字节顺序。 相比之下,MFC 客户端应用程序将 CArchive 对象与 CSocket 对象结合使用,并且 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 套接字提供的多个字节顺序转换函数。 下表介绍了这些函数。

Windows 套接字字节顺序转换函数

功能 目的
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 应用程序一样。

有关详细信息,请参阅:

另请参阅

MFC 中的 Windows 套接字