WSAEventSelect模型是一个异步事件通知模型。允许应用程序在一个或多个套接字上接收收基于事件的网络通知。它与WSAAsyncSelect模型类似,但不是依靠windows的
消息驱动机制,而是经由事件对象句柄通知。
主要函数:
WSACreateEvent 创建一个事件对象
WSAEventSelect 网络事件与事件对象进行关联。
具体参数:
socket s 套接字句柄
WSAEvent h 事件对象句柄
long NetWorkEvents FD_xxx网络事件组合。FD_ACCEPT,FD_READ,FD_WRITE,FD_CLOSE
WSAWaitForMultipleEvents 在一个或多个事件对象上等待网络事件的发生,如果指定时间已过,返回WSA_WAIT_TIMEOUT,如果调用失败则返回WSA_WAIT_FAILED
如果成功,则返回受信的事件对象。
具体参数:
DWORD cEvents 指定事件数组中句柄的个数
WSAEVENT* lphEvents 指向一个事件对象句柄数组
BOOL fWaitAll 指定是否等待所有事件对象都变成受信状态
DWORD dwtimeout
BOOL falertable 在使用WSAEventSelect模型时可以忽略,应设为FALSE
WSAEnumNetworkEvents 找到受信事件对像与之对应的套接字。
具体参数:
SOCKET S //套接字句柄。
WSAEVENT hEventObject 对应的事件对象句柄。如果提供了此参数,本函数会重置这个事件对象的状态
LPWSANETWORKEVENTS lpNetworkEvents //指向一个WSANETWORKEVENTS结构。
最后这个WSANETWORKEVENTS结构是最重要的,因为它取得了套接字上发生的网络事件和相关的出错代码。
定义如下:
LPWSANETWORKEVENTS=^WSANETWORKEVENTS
WSANETWORKEVENTS=RECORD
INetworkEvents : longint;
iErrorCode : array[0..FD_MAX_EVENTS] OF INT;
end;
iErrorCode 是一个数组,数组的每个成员对应着一个网络事件的出错代码。可以用预定义标识FD_READ_BIT,FD_WRITE_BIT,FD_ACCEPT_BIT,FD_CLOSE_BIT来索引FD_READ等事件发生的出错代码。
具体的DELPHI代码如下:
program Project1;
{$APPTYPE CONSOLE}
uses
Windows,SysUtils,JwaWinsock2;
var
eventarray :array[0..WSA_MAXIMUM_WAIT_EVENTS-1] OF WSAEVENT; //事件数组
sockarray :array[0..WSA_MAXIMUM_WAIT_EVENTS-1] of TSocket; //套接字数组
nEventTotal :Integer; //当前总事件数,不能大于64
nport :Integer; //端口
sListen :TSocket; //主套接字
v_sin :sockaddr_in;
event :WSAEVENT; //事件对象变量,用于创建新的事件
sEvent :_WSANETWORKEVENTS;
nIndex :Integer; //接收函数WSAWaitForMultipleEvents返回的值。
i,j :Integer; //循环变量
sNew :TSocket; //客户端的SOCKET
sztext :array[0..255] of AnsiChar; //接收客户端的信息。
vwsadata :WSAData; //初始化socket的变量;
begin
WSAStartup($0202,vwsadata); //初始化socket,此处用socket2.0。
try
slisten:=INVALID_SOCKET;
nport:=4567;
nEventTotal:=0;
try
sListen:=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
v_sin.sin_family:=AF_INET;
v_sin.sin_port :=htons(nport);
v_sin.sin_addr.S_addr:=INADDR_ANY;
if bind(sListen,@v_sin,SizeOf(v_sin))=SOCKET_ERROR then
begin
Writeln('绑定端口发生错误。');
end;
listen(sListen,200); //开始监听。
Writeln('网络开始进行监听中......');
//创建事件对象,并关联到新的套接字上。
event:=WSACreateEvent();
WSAEventSelect(sListen,event,FD_ACCEPT OR FD_CLOSE);
//将事件和套接字存入相应的数组中。
eventarray[nEventTotal]:=event;
sockarray [nEventTotal]:=sListen;
inc(nEventTotal);
while True do
begin
{若收到一个事件对象的网络事件通知后,便会返回一个值指出造成函数返回的事件对象,
这样应用程序便可引用事件数组中已传信的事件,并检索与事件对应的 SOCKET ,
方法是用 WSAWaitForMultipleEvents 的返回值减去相应的预定义值 WSA_WAIT_EVENT_0, 得到具体的引用值
将fAlertable设为False后,如果同时有几个事件对象受信,WSAWaitForMultipleEvents函数的返回值仅能指明
一个,就是句柄数组中最前面的那个。如果指明的事件对象总有网络时间发生,那么后面的其他事件对象所关联的
网络事件就得不到处理。解决的方法:当WSAWaitForMultipleEvents函数返回后,对每个事件都再次调用这个函数,
以便确定其状态。
}
nindex:=WSAWaitForMultipleEvents(nEventTotal,@eventarray,False,WSA_INFINITE,False);
nindex:=nIndex-WSA_WAIT_EVENT_0;
for i:=nIndex to nEventTotal-1 do
begin
nIndex:=WSAWaitForMultipleEvents(1,@eventarray[i],True,1000,False);
if (nIndex=WSA_WAIT_FAILED) or (nIndex=WSA_WAIT_TIMEOUT) then
begin
Continue;
end
else
begin
WSAEnumNetworkEvents(sockarray[i],eventarray[i],@sEvent);
if sEvent.lNetworkEvents=FD_ACCEPT then
begin
if sEvent.iErrorCode[FD_ACCEPT_BIT]=0 then
begin
if nEventTotal>WSA_MAXIMUM_WAIT_EVENTS-1 then
begin
Writeln('有太多的连接!已经大于64了。');
Continue;
end;
snew:=accept(sockarray[i],nil,nil);
writeln('有新的连接进入。');
event:=WSACreateEvent();
WSAEventSelect(sNew,event,FD_READ OR FD_CLOSE OR FD_WRITE);
eventarray[nEventTotal]:=event;
sockarray [nEventTotal]:=sNew;
inc(nEventTotal);
end;
end
else if sEvent.lNetworkEvents=FD_READ then
begin
if recv(sockarray[i],szText,SizeOf(szText),0)>0 then
begin
Writeln('接收到的信息:'+strpas(sztext));
end;
end
else if sEvent.lNetworkEvents=FD_CLOSE then
begin
if sEvent.iErrorCode[FD_CLOSE_BIT]=0 then
begin
closesocket(sockarray[i]);
for j:=i to nEventTotal-1 do
begin
sockarray[j]:=sockarray[j+1];
eventarray[j]:=eventarray[j+1];
end;
nEventTotal:=nEventTotal-1;
end;
end
else if sEvent.lNetworkEvents=FD_WRITE then
begin
end;
end;
end;
end;
finally
closesocket(sListen); //关闭套接字
end;
finally
WSACleanup; //清除socket。
end;
end.
WSAEVENTSELECT模型最多等待64个事件对象的限制,当套接字连接数量增加时,必须创建多个线程来处理I/O,也就是使用线程池。
在我写的WSAEVENTSELECT模型实例中,我做了测试发现,当我连接的数量越来越多时(当然少于64个连接),服务端的反应就越来越慢。