套接字I/O模型共有五种类型,如下:
select(选择)
WSAAsyncSelect(异步选择)
WSAEventSelect(事件选择)
overlapped(重叠)
completion port(完成端口)
*WSAAsyncSelect
Winsock通过WSAAsyncSelect()自动地设置套接字处于非阻塞方式。使用WindowsSockets实现Windows网络程序设计 的关键就是它提供了对网络事件基于消息的异步存取,用于注册应用程序感兴趣的网络事件。它请求Windows Sockets DLL在检测到套接字上发生的网络事件时,向窗口发送一个消息。
int PASCAL FAR WSAAsyncSelect(SOCKET s,HWND hWnd,unsigned int wMsg,long lEvent);
hWnd: 窗口句柄
wMsg:需要发送的消息
lEvent:事件(以下为事件的内容)
值: 含义:
FD_READ 期望在套接字上收到数据(即读准备好)时接到通知
FD_WRITE 期望在套接字上可发送数据(即写准备好)时接到通知
FD_OOB 期望在套接字上有带外数据到达时接到通知
FD_ACCEPT 期望在套接字上有外来连接时接到通知
FD_CONNECT 期望在套接字连接建立完成时接到通知
FD_CLOSE 期望在套接字关闭时接到通知
进行异步选择使用WSAAsyncSelect()函数时,有以下几点需要引起特别的注意:
.连续使用两次 WSAAsyncSelect()函数时,只有第二次设置的事件有效,如:
WSAAsyncSelect(s,hwnd,wMsg1,FD_READ);
WSAAsyncSelect(s,hwnd,wMsg2,FD_CLOSE);
这样只有当FD_CLOSE事件发生时才会发送wMsg2消息。
.可以在设置过异步选择后通过再次调用 WSAAsyncSelect(s,hwnd,0,0);的形式取消在套接字上所设置的异步事件。
.Windows Sockets DLL在一个网络事件发生后,通常只会给相应的应用程序发送一个消息,而不能发送多个消息。但通过使用一些函数隐式地允许重发此事件的消息,这样就可能再 次接收到相应的消息。
.在调用过closesocket()函数关闭套接字之后不会再发生FD_CLOSE事件。
对UDP协议,这些网络事件主要为:
FD_READ 期望在套接字收到数据(即读准备好)时接收通知;
FD_WRITE 期望在套接字可发送数(即写准备好)时接收通知;
FD_CLOSE 期望在套接字关闭时接电通知
消息变量wParam指示发生网络事件的套接字,变量1Param的低字节描述发生的网络事件,高字包含错误码。如在窗口函数的消息循环中均加一个分 支:
int ok=sizeof(SOCKADDR);
case wMsg;
switch(1Param)
{
case FD_READ: //套接字上读数据
if(recvfrom(sr.lpPlayData[j],dwDataSize,0,(struct sockaddr FAR*)&there1,
(int FAR*)&ok)==SOCKET_ERROR0) {
MessageBox(hwnd,“数据接收失败!”,“”,MB_OK);
return(FALSE);
}
case FD_WRITE: //套接字上写数据
}
break;
*WSAEventSelect
事件通知模型 要求在程序中针对使用的每个套接字创建一个事件对象,然后通过事件模式通知程序其套接字是否收到或发送的信息。一般来说这种模式,一般就是通过类似调用 waitformultipleObject一样在一个线程中等待信号事件来,来了就处理。具体调用的函数如下:
创建WSACreateEvent函数.该函数的返回值是一个创建好的事件对象句柄。事件对象句柄完后,接下来将其与某个套接字关联在一起,同时注册自 己感兴趣的网络事件类型,方法是调用WSAEventSelect函数,对它的定义如下:
int WSAEventSelect (
SOCKET s, //需要非阻塞处理的套接字
WSAEVENT hEventObject, //WSACreateEvent 创建来的,关联到socket
long lNetworkEvents
);
lNetworkEvents,对应一个“位掩码”,用于指定应用程序感兴趣的各种网络事件类型的一个组合。要想获知对这些事件类型的详细说明,请参考早 先讨论过的WSAAsyncSelect I/O模型。
为WSAEventSelect创建的事件拥有两种工作状态,以及两种工作模式。
两种工作状态分别是“已传信”(signaled)和 “未传信”(nonsignaled)。
工作模式则包括“人工”(manual reset)和“自动”(auto reset)。
WSACreateEvent缺省时其信号状态为0,且为人工设置,当网络事件触发了与一个套接字关联在一起的事件对象,其事件信号置1。在完成了一个 I/O请求的处理之后,需要调用WSAResetEvent复位处理(置信号为0)。
一个套接字同一个事件对象句柄关联在一起后,应用程序便可开始I/O处理;方法是等待网络事件触发事件对象句柄的工作状态。
一般而言,在等待网络传来事件时,类似WaitforMultipleObject,其WSAWaitForMultipleEvents函数的设计宗旨 便是用来等待一个或多个事件对象句柄,并在事先指定的一个或所有句柄进入有信号状态后,或在超过了一个规定的时间周期后,立即返回(线程往往在这里死 等)。
下面是 WSAWaitForMultipleEvents函数的定义:
DWORD WSAWaitForMultipleEvents(
DWORD cEvents ,
const WSAEVENT FAR *lphEvents ,
BOOL fWaitAll ,
DWORD dwTimeOUT ,
BOOL fAlertable
);
其用法和WaitForMultipleObject类 似。
cEvents和lphEvents参数定义了由WSAEVENT对象构成的一个数组。在这个数组中,cEvents指定的是事件对象的数 量,而lphEvents对应的是一个指针,用于直接引用该数组。
要注意的是, WSAWaitForMultipleEvents只能支持由WSA_MAXIMUM_WAIT_EVENTS对象规定的一个最大值,在此定义成64个。 故该I/O模型一次最多都只能支持64个套接字。假如想让这个模型同时管理不止64个套接字,必须创建更多的工作者线程,以便等待更多的事件对象。
fWaitAl l 参数指定了WSAWaitForMultiple Events如何等待在事件数组中的对象。
=TRUE,那 么只有等lphEvents数组内包含的所有事件对象都处于有信号状态,函数才会返回;
=FALSE,任一个事件对象进入有信号时,函数就会返回。
dwTimeout参数规定了 WSAWaitForMultipleEvents最多可等待一个网络事件发生有多长时间。超过规定的时间,函数就会立即返回。并返回 WSA_WAIT_TIMEOUT。如dwsTimeout设为WSA_INFIN ITE(永远等待),那么根据fWaiiAll或等待一个网络事件或所有网络事件都传信号后,才能从该函数退出。
fAlertable,缺省设 为FALSE。主要用于在重叠式I/O模型中.
当设置fWaiAll=false,WaitForMultipleObject再有 网络事件时,会返回一个值,指出造成函数返回的事件对象。根据WSAWaitForMultipleEvents的返回值,减去预定义值 WSA_WAIT_EVENT_0,得到具体的引用值(即索引位置),程序便可用事件数组中已发信号的事件,检索与那个事件对应的套接字,知道了造成网络 事件的套接字后,调用 WSAEnumNetworkEvents函数,调查发生了什么类型的网络事件。该函数定义如下:
int WSAEnumNetworkEvents (
SOCKET s , // 检索该套接字
WSAEVENT hEventObject ,
LPWSANETWORKEVENTS lpNetworkEvents
);
hEventObject参数则是可选的;它指定了一个事件句柄,对应于打算重设的那个事件对象。由于我们的事件对象处在一个有信号状态,所以可将它传 入,令其自动成为无信号状态。
也可以采用使用 WSAResetEvent 函数复位事件信号。
lpNetworkEvents,就是返回的结果信息,它是一个指向WSANETWORKEVENTS结构的指针,用于接收套接字上发生的网络事件类型以 及可能出现的任何错误代码。
其WSANETWORKEVENTS结构的定义:
typedef struct _WSANETWORKEVENTS
{
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;
lNetworkEvents参数指定了一个值,对应于套接字上发生的所有网络事件类型。
注意一个事件进入置1(有信号)状态时,可能会同时发生多个网络事件类型。如,一个忙的服务器可能同时收到FD_READ和FD_WRITE通知。 iErrorCode参数指定的是一个错误代码数组,同lNetworkEvents中的事件关联在一起。针对每个网络事件类型,都存在着一个特殊的事件 索引,名字与事件类型的名字类似,只是要在事件名字后面添加一个“ _BIT”后缀字串即可。如,对FD_READ事件类型来说,iErrorCode数组的索引标识符便是FD_READ_BIT。