WinSock基本I/O模型简介

时间:2021-03-01 00:07:19

如果你想在Windows平台上构建服务器应用,那么I/O模型是你必须考虑的。Windows操作系统提供了选择模型、异步选择模型、事件选择模型、重叠I/O模型和完成端口共五种I/O模型。每一种模型均适用于一种特定的应用场景。编程人员应综合考虑到程序的扩展性和可移植性等因素,做出自己的选择

1  选择模式(Select

选择模型是Winsock中最常见的I/O模型。之所以称其为“select模型,是由于它的中心思想便是利用select函数,实现对I/O的管理!最初设计该模型时,主要面向的是某些使用Unix操作系统的计算机,它们采用的是 Berkeley套接字方案。select模型已集成到Winsock 1.1中,它使那些想避免在套接字调用过程中被无辜锁定的应用程序,采取一种有序的方式,同时进行对多个套接字的管理。

2  异步选择模式(WSAAsyncSelect

使用异步选择模型,应用程序可在一个套接字上,接收以Windows消息为基础的网络事件通知。具体的做法是在建好一个套接字后,调用WSAAsyncSelect函数。

3  事件选择模式(WSAEventSelect

WSAEventSelectWSAAsyncSelect模型类似,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知。对于WSAAsyncSelect模型采用的网络事件来说,它们均可原封不动地移植到事件选择模型上。在用事件选择模型开发的应用程序中,也能接收和处理所有那些事件。该模型最主要的差别在于网络事件会投递至一个事件对象句柄,而非投递至一个窗口例程。

4  重叠I/O模式(Overlapped I/O

Winsock中,相比我们迄今为止解释过的其他所有I/O模型,重叠I/O模型使应用程序能达到更佳的系统性能。重叠模型的基本设计原理便是让应用程序使用一个重叠的数据结构,一次投递一个或多个Winsock I/O请求。针对那些提交的请求,在它们完成之后,应用程序可为它们提供服务。该模型适用于除Windows CE之外的各种Windows平台。模型的总体设计以Win32重叠I/O机制为基础。那个机制可通过ReadFileWriteFile两个函数,针对设备执行I/O操作。

5  完成端口模式(Completion Port)

完成端口模型是迄今为止最为复杂的一种I/O模型。然而,假若一个应用程序同时需要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳的系统性能!但不幸的是,该模型只适用于Windows NTWindows 2000操作系统。

因其设计的复杂性,只有在你的应用程序需要同时管理数百乃至上千个套接字的时候,而且希望随着系统内安装的CPU数量的增多,应用程序的性能也可以线性提升,才应考虑采用完成端口模型。要记住的一个基本准则是,假如要为Windows NTWindows 2000开发高性能的服务器应用,同时希望为大量套接字I/O请求提供服务(Web服务器便是这方面的典型例子),那么I/O完成端口模型便是最佳选择

 

异步选择I/O模式

Winsock提供了一个有用的异步 I/O模型。利用这个模型,应用程序可在一个套接字上,接收以Windows消息为基础的网络事件通知。具体的做法是在建好一个套接字后,调用WSAAsyncSelect函数。该模型最早出现于Winsock1.1版本中,用于帮助应用程序开发者面向一些早期的1 6Windows平台(如Windows for Workgroups ,适应其落后的多任务消息环境。应用程序仍可从这种模型中得到好处,特别是它们用一个标准的 Windows例程(常称为“winproc”),对窗口消息进行管理的时候。该模型亦得到了 Microsoft Foundation Class(微软基本类,MFC)对象CSocket的采纳。

要想使用WSAAsyncSelect模型,在应用程序中,首先必须用CreateWindow函数创建一个窗口,再为该窗口提供一个窗口例程支持函数(Winproc)。亦可使用一个对话框,为其提供一个对话例程,而非窗口例程,因为对话框本质也是窗口。设置好窗口的框架后,便可开始创建套接字,并调用WSAAsyncSelect函数,打开窗口消息通知。该函数的定义如下:

int WSAAsyncSelect(

SOCKET s,

HWND hWnd,

usigned int wMsg,

long lEvent

);

其中,s参数指定的是我们感兴趣的那个套接字。hWnd参数指定的是一个窗口句柄,它对应于网络事件发生之后,想要收到通知消息的那个窗口或对话框。wMsg参数指定在发生网络事件时,打算接收的消息。该消息会投递到由hWnd窗口句柄指定的那个窗口。通常,应用程序需要将这个消息设为比 WindowsWM_USER大的一个值,避免网络窗口消息与预定义的标准窗口消息发生混淆与冲突。最后一个参数是lEvent,它指定的是一个位掩码,对应于一系列网络事件的组合(请参考表2-1 ,应用程序感兴趣的便是这一系列事件。大多数应用程序通常感兴趣的网络事件类型包括:FD_READFD_WRITEFD_ACCEPTFD_CONNECTFD_CLOSE。当然,到底使用FD_ACCEPT,还是使用FD_CONNECT类型,要取决于应用程序的身份到底是一个客户机呢,还是一个服务器。如应用程序同时对多个网络事件有兴趣,只需对各种类型执行一次简单的按位OR(或)运算,然后将它们分配给lEvent就可以了。举个例子来说:

WSAAsyncSelect(s, hwnd, WM_SOCKET,

FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE);

这样一来,我们的应用程序以后便可在套接字s上,接收到有关连接、发送、接收以及套接字关闭这一系列网络事件的通知。特别要注意的是,多个事件务必在套接字上一次注册!另外还要注意的是,一旦在某个套接字上允许了事件通知,那么以后除非明确调用closesocket命令,或者由应用程序针对那个套接字调用了WSAAsyncSelect,从而更改了注册的网络事件类型,否则的话,事件通知会永远有效!若将lEvent参数设为0,效果相当于停止在套接字上进行的所有网络事件通知。

若应用程序针对一个套接字调用了WSAAsyncSelect,那么套接字的模式会从锁定自动变成非锁定,我们在前面已提到过这一点。这样一来,假如调用了像WSARecv这样的Winsock I/O函数,但当时却并没有数据可用,那么必然会造成调用的失败,并返回WSAEWOULDBLOCK错误。为防止这一点,应用程序应依赖于由WSAAsyncSelectuMsg参数指定的用户自定义窗口消息,来判断网络事件类型何时在套接字上发生;而不应盲目地进行调用。

WinSock基本I/O模型简介

3-1 用于WSAAsyncSelect函数的网络事件类型

应用程序在一个套接字上成功调用WSAAsyncSelect之后,应用程序会在与hWnd窗口句柄参数对应的窗口例程中,以Windows消息的形式,接收网络事件通知。窗口例程通常定义如下:

LRESULT CALLBACK WindowProc(

              HWND hWnd,

              UINT uMsg,

              WPARAM wParam,

              LPARAM lParam

       );

其中,hWnd参数指定一个窗口的句柄,对窗口例程的调用正是由那个窗口发出的。uMsg参数指定需要对哪些消息进行处理。就我们的情况来说,感兴趣的是WSAAsyncSelect调用中定义的消息。wParam参数指定在其上面发生了一个网络事件的套接字。假若同时为这个窗口例程分配了多个套接字,这个参数的重要性便显示出来了。在lParam参数中,包含了两方面重要的信息。其中,lParam的低字(低位字)指定了已经发生的网络事件,而lParam的高字(高位字)包含了可能出现的任何错误代码。

网络事件消息抵达一个窗口例程后,应用程序首先应检查 lParam 的高字位,以判断是否在套接字上发生了一个网络错误。这里有一个特殊的宏: WSAGETSELECTERROR ,可用它返回高字位包含的错误信息。若应用程序发现套接字上没有产生任何错误,接着便应调查到底是哪个网络事件类型,造成了这条 Windows 消息的触发 具体的做法便是读取 lParam 之低字位的内容。此时可使用另一个特殊的宏: WSAGETSELECTEVENT ,用它返回 lParam 的低字部分。