网络学习之WSAEventSelect模型

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个连接),服务端的反应就越来越慢。

posted @ 2011-05-05 23:48  菜程序员  阅读(1334)  评论(0编辑  收藏  举报