WSAEventSelect模型

5 篇文章 0 订阅

 事件通知模型:要求我们的应用程序针对打算使用的每一个套接字,首先创建一个事件对象。创建方法是调用WSACreateEvent函数,它的定义如下: WSAEVENT WSACreateEvent(void);

   函数的返回值很简单,就是一个创建好的事件对象句柄。事件对象句柄到手后,接下来必须将其与某个套接字关联在一起,同时注册自己感兴趣的网络事件类型。调用WSAEventSelect来做到这一点,定义如下:

   int WSAEventSelect(

       SOCKET s,  // 定义的套接字

       WSAEVENT hEventObject, // 与套接字关联在一起的事件对象句柄

       long lNetworkEvent // 应用程序感兴趣的各种网络事件类型的一个组合(FD_READ|FD_WRITE|FD_ClOSE)

   ); 

   WSAEventSelect创建的事件拥有两种工作状态,以及两种工作模式。其中,两种工作状态分别是“已传信”(signaled)和“未传信”(non signaled)。工作模式则包括“人工重设”(manual reset)和“自动重设”(auto reset)。WSAEventSelect最开始在一种未传信的工作状态中,并用一种人工重设模式,来创建事件句柄。随着网络事件触发了与一个套接字关联在一起的事件对象,工作状态便会从“未传信”转变成“已传信”。由于事件对象是在一种人工重设模式中创建的,所以在完成了一个I/O请求的处理之后,我们的应用程序需要负责将工作状态从已传信更改为未传信。要做到这一点,可调用WSAResetEvent函数,对它的定义如下:BOOL WSAResetEvent(WSAEVENT hEvent);唯一的参数是前面用WSACreateEvent函数创建的事件对象句柄,成功返回TRUE,失败返回FALSE。当应用程序完成了对一个事件对象的处理后,应调用BOOL WSACloseEvent(WSAEVENT hEvent);函数释放由hEvent句柄占用的系统资源。成功返回TRUE,失败返回FALSE。

   一个套接字同一个事件对象句柄关联在一起后,应用程序便可开始I/O处理;方法是等待网络事件触发事件对象句柄的工作状态。WSAWaitForMultipleEvents函数的设计宗旨便是用来等待一个或多个事件对象句柄,并在事先指定的一个或所有句柄进入“已传信”状态后,或在超过了一个规定的时间周期后,立即返回。定义如下:

 DWORD WSAWaitForMultipleEvents(

          DWORD cEvents, 

          const WSAEVENT FAR * lphEvents,

          BOOL fWaitAll,

          DWORD dwTimeout,

          BOOL fAlertable

     );

    其中,cEvents和lphEvents参数定义了由WSAEVENT对象构成的一个数组。在这个数组中,cEvents指定的是事件对象的数量,而lphEvents对应的是一个指针,用于直接引用该数组。要注意的是,WSAWaitForMultipleEvents只能支持由WS AMAXIMUMWAITEVENTS对象规定的一个最大值,在此定义成64个。因此,针对发出WSAWaitForMultipleEvents调用的每个线程,该I/O模型一次最多都只能支持64个套接字。假如想让这个模型同时管理不止64个套接字,必须创建额外的工作者线程,以便等待更多的事件对象。f WaitAll参数指定了WSAWaitForMultipleEvents如何等待在事件数组中的对象。若设为T RUE,那么只有等lphEvents数组内包含的所有事件对象都已进入“已传信”状态,函数才会返回;但若设为FALSE,任何一个事件对象进入“已传信”状态,函数就会返回。就后一种情况来说,返回值指出了到底是哪个事件对象造成了函数的返回。通常,应用程序应将该参数设为FALSE,一次只为一个套接字事件提供服务。dwTimeout参数规定了WSAWaitForMul tipleEvents最多可等待一个网络事件发生有多长时间,以毫秒为单位,这是一项“超时”设定。超过规定的时间,函数就会立即返回,即使由fWaitAll参数规定的条件尚未满足也如此。如超时值为0,函数会检测指定的事件对象的状态,并立即返回。这样一来,应用程序实际便可实现对事件对象的“轮询”。但考虑到它对性能造成的影响,还是应尽量避免将超时值设为0。假如没有等待处理的事件,WSAWaitForMultipleEvents便会返回WSAWAITTIMEOUT。如dwsTimeout设为WSA INFINITE(永远等待),那么只有在一个网络事件传信了一个事件对象后,函数才会返回。最后一个参数是fAlertable,在我们使用WSAEventSelect模型的时候,它是可以忽略的,且应设为FALSE。该参数主要用于在重叠式I/O模型中,在完成例程的处理过程中使用。

   若WSAWaitForMultipleEvents收到一个事件对象的网络事件通知,便会返回一个值,指出造成函数返回的事件对象。这样一来,我们的应用程序便可引用事件数组中已传信的事件,并检索与那个事件对应的套接字,判断到底是在哪个套接字上,发生了什么网络事件类型。对事件数组中的事件进行引用时,应该用WSAWaitForMultipleEvents的返回值,减去预定义值WSAWAITEVENT0,得到具体的引用值(即索引位置)。如下例所示:

   nIndex = WSAWaitForMultipleEvents(......);

   hEvent = hEventArray[nIndex - WSA_WAIT_EVENT_0];

   知道了造成网络事件发生的套接字后,调用WSAEnumNetworkEvents函数,调查发生了什么类型的网络事件,定义如下:

   int WSAEnumNetworkEvents(

       SOCKET s,  // 网络事件发生相关联的套接字

       WSAEVENT hEventObject, //重设事件对象,将处在“已传信”改为“未传信”,亦可使用WSAResetEvent替代

       LPWSAMETWORKEVENTS lpNetworkEvents // 接收套接字上发生的网络事件类型以及可能出现的错误代码

   );

   typedef struct _WSANETWORKEVENTS {

          long lNetworkEvent;

          int iErrorCode[FD_MAX_EVENTS];

    }WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;

    lNetworkEvents参数指定了一个值,对应于套接字上发生的所有网络事件类型。注意一个事件进入传信状态时,可能会同时发生多个网络事件类型。例如,一个繁忙的服务器应用可能同时收到FD_READ和FD_WRITE通知。iErrorCode参数指定的是一个错误代码数组,同lNetworkEvents中的事件关联在一起。针对每个网络事件类型,都存在着一个特殊的事件索引,名字与事件类型的名字类似,只是要在事件名字后面添加一个“_BIT”后缀字串即可。例如,对FD_READ事件类型来说,iErrorCode数组的索引标识符便是FD_READ_BIT。下述代码片断对此进行了阐释(针对FDREAD事件):

    if (NetworkEvents.lNetworkEvents & FD_READ) {

        if (NetworkEvents.iErrorCode[FD_READ_BIT] != 0) {

        }

    }

    完成了对WSANETWORKEVENTS结构中的事件的处理之后,我们的应用程序应在所有可用的套接字上,继续等待更多的网络事件,以下是一段服务器端代码

 

  1. #include <winsock2.h>  
  2. #include <iostream>  
  3. #define PORT    5150  
  4. #define MSGSIZE 1024  
  5. #pragma comment(lib, "ws2_32.lib")  
  6.   
  7. int      g_iTotalConn = 0;  
  8. SOCKET   g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];  
  9. WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS];  
  10.   
  11. DWORD WINAPI WorkerThread(LPVOID);  
  12. int CALLBACK ConditionFunc(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData);    
  13. void Cleanup(int index);  
  14.   
  15. int main()  
  16. {  
  17.     WSADATA     wsaData;  
  18.     SOCKET      sListen;  
  19.     SOCKADDR_IN local, client;  
  20.     DWORD       dwThreadId;  
  21.     // Initialize Windows Socket library  
  22.     WSAStartup(0x0202, &wsaData);  
  23.     // Create listening socket  
  24.     sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  
  25.     // Bind  
  26.     local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);  
  27.     local.sin_family = AF_INET;  
  28.     local.sin_port = htons(PORT);  
  29.     bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));  
  30.     // Listen  
  31.     listen(sListen, 3);  
  32.   
  33.     //把监听套接字也放进socket队列  
  34.     g_CliSocketArr[g_iTotalConn] = sListen;  
  35.     g_CliEventArr[g_iTotalConn] = WSACreateEvent();  
  36.     WSAEventSelect(g_CliSocketArr[g_iTotalConn], g_CliEventArr[g_iTotalConn], FD_ACCEPT | FD_CLOSE);  
  37.     g_iTotalConn++;  
  38.   
  39.     // Create worker thread  
  40.     CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);  
  41.     while (TRUE)  
  42.     {  
  43.         ;  
  44.     }  
  45. }  
  46. DWORD WINAPI WorkerThread(LPVOID lpParam)  
  47. {  
  48.     int              ret, index;  
  49.     WSANETWORKEVENTS NetworkEvents;  
  50.     char             szMessage[MSGSIZE];  
  51.     while (TRUE)  
  52.     {  
  53.         ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);  
  54.         if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)  
  55.         {  
  56.             continue;  
  57.         }  
  58.         index = ret - WSA_WAIT_EVENT_0;  
  59.         WSAEnumNetworkEvents(g_CliSocketArr[index], g_CliEventArr[index], &NetworkEvents);  
  60.   
  61.         switch(NetworkEvents.lNetworkEvents)  
  62.         {  
  63.         case FD_ACCEPT :  
  64.             if(NetworkEvents.iErrorCode[FD_ACCEPT_BIT] == 0)  
  65.             {  
  66.   
  67.                 if(g_iTotalConn >= WSA_MAXIMUM_WAIT_EVENTS)  
  68.                 {  
  69.                     printf("Too much connections!");  
  70.                     continue;  
  71.                 }  
  72.   
  73.                 int iaddrSize = sizeof(SOCKADDR_IN);  
  74.                 SOCKADDR_IN client;  
  75.                 SOCKET  sClient = WSAAccept(g_CliSocketArr[index], (sockaddr*)&client, &iaddrSize, NULL, 0);  
  76.                 printf("%d:Accepted client:%s:%d:%d\n", g_iTotalConn, inet_ntoa(client.sin_addr), ntohs(client.sin_port), sClient);           
  77.                 g_CliSocketArr[g_iTotalConn] = sClient;  
  78.                 g_CliEventArr[g_iTotalConn] = WSACreateEvent();  
  79.                 WSAEventSelect(g_CliSocketArr[g_iTotalConn], g_CliEventArr[g_iTotalConn], FD_READ | FD_CLOSE | FD_WRITE);  
  80.                 g_iTotalConn++;           
  81.             }  
  82.             break;  
  83.   
  84.         case FD_READ :  
  85.             // Receive message from client  
  86.             if(NetworkEvents.iErrorCode[FD_READ_BIT] == 0)  
  87.             {  
  88.                 ret = recv(g_CliSocketArr[index], szMessage, MSGSIZE, 0);  
  89.                 if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))  
  90.                 {  
  91.                     Cleanup(index);  
  92.                 }  
  93.             }  
  94.             break;  
  95.   
  96.         case FD_WRITE :  
  97.             // Receive message from client  
  98.             if(NetworkEvents.iErrorCode[FD_WRITE_BIT] == 0)  
  99.             {  
  100.                 strcpy(szMessage, "Server");  
  101.                 std::cout<<index<<":"<<szMessage<<std::endl;  
  102.                 send(g_CliSocketArr[index], szMessage, strlen(szMessage), 0);  
  103.             }  
  104.             break;  
  105.   
  106.         case FD_CLOSE :  
  107.             if(NetworkEvents.iErrorCode[FD_CLOSE_BIT] == 0)  
  108.             {  
  109.                 printf("Close client:%d\n", g_CliSocketArr[index]);  
  110.                 Cleanup(index);  
  111.             }  
  112.             break;  
  113.         }  
  114.     }  
  115.     return 0;  
  116. }  
  117. void Cleanup(int index)  
  118. {  
  119.     closesocket(g_CliSocketArr[index]);  
  120.     WSACloseEvent(g_CliEventArr[index]);  
  121.     if (index < g_iTotalConn - 1)  
  122.     {  
  123.         g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn - 1];  
  124.         g_CliEventArr[index] = g_CliEventArr[g_iTotalConn - 1];  
  125.     }  
  126.   
  127.     g_iTotalConn--;  
  128. }  
  129.   
  130. int CALLBACK ConditionFunc(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)    
  131. {    
  132.     if (g_iTotalConn< WSA_MAXIMUM_WAIT_EVENTS)    
  133.         return CF_ACCEPT;    
  134.     else    
  135.         return CF_REJECT;    
  136. }   




 

FD_READ 事件非常容易掌握. 当有数据发送过来时, WinSock会以FD_READ事件通知你, 对于每一个FD_READ事件,你需要像下面这样调用recv(): int nRecvData = recv(wParam, &data, sizeof(data), 0); 基本上就是这样, 别忘了修改上面的wParam。还有,不一定每一次调用recv()都会接收到一个完整的数据包, 因为数据可能不会一次性全部发送过来. 所以在开始处理接收到的数据之前, 最好对接收到的字节数(即recv()的返回值)进行判断,看看是否收到的是一个完整的数据包。

  

  FD_WRITE相对来说就麻烦一些。首先,当你建立了一个连接时,会产生一个FD_WRITE事件。但是如果你认为在收到 FD_WRITE时调用send()就万事大吉,那就错了。FD_WRITE事件只在发送缓冲区有多出的空位,可以容纳需要发送的数据时才会触发。

   上面所谓的发送缓冲区,是指系统底层提供的缓冲区。send()先将数据写入到发送缓冲区中,然后通过网络发送到接收端。你或许会想,只要不把发送缓冲区填满,让发送缓冲区保持足够多的空位容纳需要发送的数据,那么你就会源源不断地收到FD_WRITE事件了。嘿嘿,错了。上面只是说FD_WRITE事件在发送缓冲区有多出的空位时会触发,但不是在有足够的空位时触发,就是说你得先把发送缓冲区填满。

   通常的办法是在一个无限循环中不断的发送数据,直到把发送缓冲区填满。当发送缓冲区被填满后,send()将会返回 SOCKET_ERROR,WSAGetLastError()会返回WSAWOULDBLOCK。如果当前这个SOCKET处于阻塞(同步)模式,程序会一直等待直到发送缓冲区空出位置然后发送数据;如果SOCKET是非阻塞(异步)的,那么你就会得到WSAWOULDBLOCK错误。于是只要我们首先循环调用send()直到发送缓冲区被填满,然后当缓冲区空出位置来的时候,系统就会发出FD_WRITE事件。下面是一个处理FD_WRITE事件的例子。

   case FD_WRITE:  // 可以发送数据了
   { 
      // 进入无限循环
      while(TRUE)
      {
          // 从文件中读取数据,保存到packet.data里面.
          in.read((char*)&packet.data,MAX_PACKET_SIZE);

          // 发送数据
          if (send(wparam, (char*)(&packet), sizeof(PACKET), 0) == SOCKET_ERROR)
          {
              if (WSAGetLastError() == WSAEWOULDBLOCK)
              {
                  // 发送缓冲区已经满了, 退出循环.
                  break;
              }
              else // 其他错误
              {
                 // 显示出错信息然后退出.
                 CleanUp();
                 return(0);
              }
           }
        }
     } break;

    看到了吧,实现其实一点也不困难。只是弄混了一些概念而已。使用这样的发送方式,在发送缓冲区变满的时候就可以退出循环。然后,当缓冲区空出位置来的时候,系统会触发另外一个FD_WRITE事件,于是你就可以继续发送数据了。

    在你开始使用新学到的知识之前,我还想说明一下FD_WRITE事件的使用时机。如果你不是一次性发送大批量的数据的话,就别想着使用FD_WRITE事件了,原因很简单-如果你寄期望于在收到FD_WRITE事件时发送数据,但是却又不能发送足够的数据填满发送缓冲区,那么你就只能收到连接刚刚建立时触发的那一次FD_WRITE-系统不会触发更多的FD_WRITE了。所以当你只是发送尽可能少的数据的时候,就忘掉 FD_WRITE 机制吧,在任何你想发送数据的时候直接调用send()。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值