Winsock提供了五种 IO模型:选择(Select)、异步选择(WSAAsyncSelect)、事件选择(WSAEventSelect)、重叠I/O(Overlapped I/O)和完成端口(Completion Port)共五种I/O模型。每种模型都有各自的特点,应用程序应根据实际需要选择合适的IO模型。 1. 选择模型
之所以叫做“选择”模型,是因为该模型以select函数为核心,该模型最初主要是面向UNIX操作系统,使用该模型可以避免程序阻塞或在非阻塞模式下IO操作返回错误。 如果你选择的是同步socket,读写操作会被阻塞,这可以通过多线程来解决,但是如果有多个socket需要进行读写,使用多线程的方法就十分有限了。选择异步socket不会阻塞,但在socket不可操作时进行读写会返回错误,而且也不知何时socket才能可以读写,轮询的方式又会降低程序的性能,使用select则可以解决这样的问题。
在这个模型中,你可以通过select函数选择你所关心的socket,select会为你监听你所关心的socket的状态,直到这些socket可以操作select才返回。下面给出一个典型的选择模型开发步骤:
2. 异步选择模型
在这个模型中,应用程序可以在socket上接收以windows消息为基础的网络事件通知,根据通知内容进行执行相应的动作,如果你在开发一个界面应用程序,希望处理网络事件像处理一般的消息一样,选择这个模型就非常合适。这个模型中的关键函数为WSAAsyncSelect,原型如下:
int WSAAsyncSelect (
SOCKET s, //你所关注的socket句柄
HWND hWnd, //用于接收socket消息的窗口句柄
unsigned int wMsg, //一个你自己定义的消息,网络事件以此消息通知 long lEvent //网络事件掩码 );
这个函数将socket和窗口通过消息建立关联,在socket上有事件发生时,系统会将该函数指定的消息发送给该函数所指定窗口,然后系统再调用窗口过程处理该消息。在窗口收到的消息中,wParam 就是socket句柄 ,lParam代表网络事件和错误代码,window提供两个宏:WSAGETSELECTERROR(lParam) 提取错误代码,通过WSAGetLastError可以获得错误信息;WSAGETSELECTEVENT(lParam)提取网络事件,网络事件常用的有FD_READ|FD_WRITE|FD_CLOSE|FD_ACCEPT|FD_CONNECTION等,这样我们收到一个socket消息后,就知道了在哪个socket上发生了什么事件。 3. 事件选择模型
异步选择模型以处理窗口消息的方式处理网络事件,对于开发窗口程序比较方便,而对于一个没有窗口的程序则需要建立一个隐藏窗口。针对异步选择模型的不足,WinSocket提供了事件选择模型,该模型与异步选择模型类似,只不过它是基于内核事件而异步选择模型基于窗口。
我们通过WSAEventSelect将socket与内核事件建立关联,原型如下:
int WSAEventSelect (
SOCKET s, //socket WSAEVENT hEventObject, //内核事件 long lNetworkEvents //网络事件 );
WinSocket对内核事件进行了封装,如将CreateEvent 封装为WSACreateEvent,将HANDLE封装为WSAEVENT,两者使用方法类似。WSAEVENT 与EVENT一样有两种状态:有信号、无信号,等待事件信号的方法也类似,普遍的EVENT使用WaitForMultipleObjects,而等待WSAEVENT信号使用WSAWaitForMultipleEvents,原型如下: DWORD WSAWaitForMultipleEvents(
DWORD cEvents, //第二个参数WSAEVENT 的总数
const WSAEVENT FAR *lphEvents, // WSAEVENT指针,通过数组传入多个 BOOL fWaitAll, //是否等待全部事件都有信号才返回 DWORD dwTimeOUT, //超时
BOOL fAlertable //在该模型中不用,固定设为FALSE );
如何知道是哪个socket发生事件?用WSAWaitForMultipleEvents的返回值减去WSA_WAIT_EVENT_0就是第二个参数lphEvents指向的数组标号,为此要将保存的socket句柄数组和事件数组一一对应,知道数组标号,也就知道了发生事件的socket。
知道了哪个socket,如何知道在这个socket上发生了什么事件?通过函数WSAEnumNetworkEvents 可以获得,原型如下: int WSAEnumNetworkEvents (
SOCKET s, WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents );
前两个参数我们已经知道,第三个参数为WSANETWORKEVENTS的结构体指针,该结构体定义如下:
typedef struct _WSANETWORKEVENTS { long lNetworkEvents; //事件代码 int iErrorCode[FD_MAX_EVENTS]; //错误代码 } WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;
将事件与“求与”如FD_READ, 如果结果非零,则说明该事件发生了 if (FD_READ & tNetEvents.lNetworkEvents) // 收到数据 { if (0 != tNetEvents.iErrorCode[FD_READ_BIT]) //错误代码 }
4. 重叠IO模型
与之前的IO模型相比,重叠IO模型具有较高的读写效率,在投递一个IO操作后,系统直接操作应用程序中的缓冲区,而不是先操作socket缓冲区之后再拷贝到应用程序的缓冲区。该模型效率高,但使用上也比较复杂,比较适合实时数据采集或者在传输大的文件时应用,大致开发步骤如下:
1)创建一个套接字,开始在指定的端口上监听连接请求。 2)接受一个进入的连接请求。
3)为接受的套接字新建一个WSAOVERLAPPED结构,并为该结构分配一个事件对象句柄。也将事件对象句柄分配给一个事件数组,以便稍后由WSAWaitForMultipleEvents函数使用。
4)在套接字上投递一个异步WSARecv请求,指定参数为WSAOVERLAPPED结构。 5)使用步骤3)的事件数组,调用WSAWaitForMultipleEvents函数,并等待与重叠调用关联在一起的事件进入\已传信\状态
6)WSAWaitForMultipleEvents函数完成后,事件数组,调用WSAResetEvent函数,从而重设事件对象,并对完成的重叠请求进行处理.
7)使用WSAGetOverlappedResult函数,判断重叠调用的返回状态是什么. 8)在套接字上投递另一个重叠WSARecv请求. 9)重复步骤5~8.
5.完成端口模型
完成端口模型据称是目前效率最高也最复杂的模型,但它只适用于NT和2000系统,在一个应用程序需要与大量的客户端连接时,比较适合使用此模型,如开发游戏服务器。它的主要思想是将众多的socket映射到一个叫完成端口的对象上,通过这个完成端口来管理与客户端的通信。在创建这个完成端口时需要指定CPU数量,根据CPU数量建立相应的线程,CPU数量越大,应用程序运行效率越高。
四、基于MFC的socket编程 1、CAsyncSocket 这个类实际上是对socket异步选择模型的封装,在建立一个CAsyncSocket对象后,它内部创建了一个隐藏的CSocketWnd窗口,该窗口根据收到的消息类型调用CAsyncSocket定义虚函数,这样我们只需要重载这些虚函数完成所需要的操作即可。 OnAccept OnClose OnConnect OnReceive OnSend 通知侦听套接字,它可以通过调用Accept,接受挂起连接请求 通知套接字,关闭对它的套接字连接 通知连接套接字,连接尝试已经完成,无论成功或失败 通知侦听套接字,通过调用Receive恢复数据 通知套接字,通过调用Send,它可以发送数据 OnOutOfBandData 通知接收套接字,在套接字上有带外数据读入,通常是忙消息 服务器端:
声明一个CAsyncSocket对象 Create(port), 该端口用于bind Bind()绑定服务器地址
处理消息通知,即上面所列出的需要函数 客户端:
声明一个CAsyncSocket对象 Create(port),该端口默认为0,由系统分配端口 Connect(), 如果是UDP 则不用这步,注意CAsyncSocket默认构造TCP,UDP需要在构造函数中指定
处理消息通知,即上面所列出的需要函数 2、CSocket
CSocket只是在CAsyncSocket上面做了扩展,增加了同步通信功能,其他保持不变,使用方法也是一样。
Winsocket编程基础



