程式扎記: [ NP in MS ] Winsock I/O Methods (Part2 : Socket I/O Models 之二 - WSAEventSelect Model)

標籤

2011年5月3日 星期二

[ NP in MS ] Winsock I/O Methods (Part2 : Socket I/O Models 之二 - WSAEventSelect Model)

The WSAEventSelect Model : 
Winsock provides another useful asynchronous event notification I/O model that is similar to the WSAAsyncSelect model that allows an application to receive event-based notification of network events on one or more sockets. This model is similar to the WSAAsyncSelect model because your application receives and processes the same network events listed in Table 5-3 that the WSAAsyncSelect model uses. The major difference with this model is that network events are notified via an event object handle instead of a window procedure. 
- Event Notification 
The event notification model requires your application to create an event object for each socket used by calling the WSACreateEvent function, which is defined as : 

  1. WSAEVENT WSACreateEvent(void);  
The WSACreateEvent function simply returns a manual reset event object handle. Once you have an event object handle, you have to associate it with a socket and register the network event types of interest, as shown in Table 5-3. This is accomplished by calling the WSAEventSelect function, which is defined as : 
  1. int WSAEventSelect(  
  2.     SOCKET s,  
  3.     WSAEVENT hEventObject,  
  4.     long lNetworkEvents  
  5. );  
The s parameter represents the socket of interest. The hEventObject parameter represents the event object—obtained with WSACreateEvent—to associate with the socket. The last parameter, lNetworkEvents, represents a bitmask that specifies a combination of network event types (listed in Table 5-3) that the application is interested in. For a detailed discussion of these event types, see the WSAAsyncSelect I/O model discussed previously. 
The event created for WSAEventSelect has two operating states and two operating modes. The operating states are known as signaled and non-signaled. The operating modes are known as manual reset and auto reset. WSACreateEvent initially creates event handles in a non-signaled operating state with a manual reset operating mode. As network events trigger an event object associated with a socket, the operating state changes from non-signaled to signaled. Because the event object is created in a manual reset mode, your application is responsible for changing the operating state from signaled to non-signaled after processing an I/O request. This can be accomplished by calling the WSAResetEvent function, which is defined as : 
  1. BOOL WSAResetEvent(WSAEVENT hEvent);  
The function takes an event handle as its only parameter and returns TRUE or FALSE based on the success or failure of the call. When an application is finished with an event object, it should call the WSACloseEvent function to free the system resources used by an event handle. The WSACloseEvent function is defined as : 
  1. BOOL WSACloseEvent(WSAEVENT hEvent);  
This function also takes an event handle as its only parameter and returns TRUE if successful or FALSE if the call fails. 
Once a socket is associated with an event object handle, the application can begin processing I/O by waiting for network events to trigger the operating state of the event object handle. The WSAWaitForMultipleEvents function is designed to wait on one or more event object handles and returns either when one or all of the specified handles are in the signaled state or when a specified timeout interval expires. WSAWaitForMultipleEvents is defined as : 
  1. DWORD WSAWaitForMultipleEvents(  
  2.     DWORD cEvents,  
  3.     const WSAEVENT FAR * lphEvents,  
  4.     BOOL fWaitAll,  
  5.     DWORD dwTimeout,  
  6.     BOOL fAlertable  
  7. );  
The cEvents and lphEvents parameters define an array of WSAEVENT objects in which cEvents is the number of event objects in the array and lphEvents is a pointer to the array. WSAWaitForMultipleEvents can support only a maximum of WSA_MAXIMUM_WAIT_EVENTS objects, which is defined as 64. Therefore, this I/O model is capable of supporting only a maximum of 64 sockets at a time for each thread that makes the WSAWaitForMultipleEvents call. If you need to have this model manage more than 64 sockets, you should create additional worker threads to wait on more event objects. The fWaitAll parameter specifies how WSAWaitForMultipleEvents waits for objects in the event array. If TRUE, the function returns when all event objects in the lphEvents array are signaled. If FALSE, the function returns when any one of the event objects is signaled. In the latter case, the return value indicates which event object caused the function to return. Typically, applications set this parameter to FALSE and service one socket event at a time. The dwTimeout parameter specifies how long (in milliseconds) WSAWaitForMultipleEvents will wait for a network event to occur. The function returns if the interval expires, even if conditions specified by thefWaitAll parameter are not satisfied. If the timeout value is 0, the function tests the state of the specified event objects and returns immediately, which effectively allows an application to poll on the event objects. If no events are ready for processing, WSAWaitForMultipleEvents returns WSA_WAIT_TIMEOUT. If dwsTimeout is set to WSA_INFINITE, the function returns only when a network event signals an event object. The final parameter, fAlertable, can be ignored when you're using the WSAEventSelect model and should be set to FALSE. It is intended for use in processing completion routines in the overlapped I/O model, which will be described later in this chapter. 
Note that by servicing signaled events one at a time (by setting the fWaitAll parameter to FALSE), it is possible to starve sockets toward the end of the event array. Consider the following code: 
  1. WSAEVENT                HandleArray[WSA_MAXIMUM_WAIT_EVENTS];  
  2. int             WaitCount=0, ret, index;  
  3.   
  4. // Assign event handles into HandleArray  
  5. while (1) {  
  6.         ret = WSAWaitForMultipleEvents(  
  7.             WaitCount,   
  8.             HandleArray,   
  9.             FALSE,   
  10.             WSA_INFINITE,   
  11.             TRUE);  
  12.     if ((ret != WSA_WAIT_FAILED) && (ret != WSA_WAIT_TIMEOUT)) {  
  13.         index = ret - WSA_WAIT_OBJECT_0;  
  14.             // Service event signaled on HandleArray[index]  
  15.             WSAResetEvent(HandleArray[index]);  
  16.         }  
  17. }  
If the socket connection associated in index 0 of the event array is continually receiving data such that after the event is reset additional data arrives causing the event to be signaled again, the rest of the events in the array are starved. This is clearly undesirable. Once an event within the loop is signaled and handled, all events in the array should be checked to see if they are signaled as well. This can be accomplished by using WSAWaitForMultipleEvents with each individual event handle after the first signaled event and specifying a dwTimeOut of zero. 
When WSAWaitForMultipleEvents receives network event notification of an event object, it returns a value indicating the event object that caused the function to return. As a result, your application can determine which network event type is available on a particular socket by referencing the signaled event in the event array and matching it with the socket associated with the event. When you reference the events in the event array, you should reference them using the return value of WSAWaitForMultipleEvents minus the predefined value WSA_WAIT_EVENT_0. For example : 
  1. Index = WSAWaitForMultipleEvents(...);  
  2. MyEvent = EventArray[Index - WSA_WAIT_EVENT_0];  
Once you have the socket that caused the network event, you can determine which network events are available by calling the WSAEnumNetworkEventsfunction, which is defined as : 
  1. int WSAEnumNetworkEvents(  
  2.     SOCKET s,  
  3.     WSAEVENT hEventObject,  
  4.     LPWSANETWORKEVENTS lpNetworkEvents  
  5. );  
The s parameter represents the socket that caused the network event, and the hEventObject parameter is an optional parameter representing an event handle identifying an associated event object to be reset. Because our event object is in a signaled state, we can pass it in and it will be set to a non-signaled state. The hEventObject parameter is optional in case you wish to reset the event manually via the WSAResetEvent function. The final parameter,lpNetworkEvents, takes a pointer to a WSANETWORKEVENTS structure, which is used to retrieve network event types that occurred on the socket and any associated error codes. The WSANETWORKEVENTS structure is defined as : 
  1. typedef struct _WSANETWORKEVENTS  
  2. {  
  3.     long lNetworkEvents;  
  4.     int  iErrorCode[FD_MAX_EVENTS];  
  5. } WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;  
The lNetworkEvents parameter is a value that indicates all the network event types (see Table 5-3) that have occurred on the socket. 
Ps. 
More than one network event type can occur whenever an event is signaled. For example, a busy server application might receive FD_READ and FD_WRITE notification at the same time.

The iErrorCode parameter is an array of error codes associated with the events in lNetworkEvents. For each network event type, there is a special event index similar to the event type names—except for an additional “_BIT” string appended to the event name. For example, for the FD_READ event type, the index identifier for the iErrorCode array is named FD_READ_BIT. The following code fragment demonstrates this for an FD_READ event : 
  1. // Process FD_READ notification  
  2. if (NetworkEvents.lNetworkEvents & FD_READ)  
  3. {  
  4.     if (NetworkEvents.iErrorCode[FD_READ_BIT] != 0)  
  5.     {  
  6.        printf("FD_READ failed with error %d\n",   
  7.            NetworkEvents.iErrorCode[FD_READ_BIT]);  
  8.     }  
  9. }  
After you process the events in the WSANETWORKEVENTS structure, your application should continue waiting for more network events on all of the available sockets. The following example demonstrates how to develop a server and manage event objects when using the WSAEventSelect I/O model. The code highlights the steps needed to develop a basic server application capable of managing one or more sockets at a time : 
  1. SOCKET SocketArray [WSA_MAXIMUM_WAIT_EVENTS];  
  2. WSAEVENT EventArray [WSA_MAXIMUM_WAIT_EVENTS],  
  3.          NewEvent;  
  4. SOCKADDR_IN InternetAddr;  
  5. SOCKET Accept, Listen;  
  6. DWORD EventTotal = 0;  
  7. DWORD Index, i;  
  8. // Set up a TCP socket for listening on port 5150  
  9. Listen = socket (PF_INET, SOCK_STREAM, 0);  
  10.   
  11. InternetAddr.sin_family = AF_INET;  
  12. InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);  
  13. InternetAddr.sin_port = htons(5150);  
  14.   
  15. bind(Listen, (PSOCKADDR) &InternetAddr,  
  16.     sizeof(InternetAddr));  
  17.   
  18. NewEvent = WSACreateEvent();  
  19.   
  20. WSAEventSelect(Listen, NewEvent,  
  21.     FD_ACCEPT │ FD_CLOSE);  
  22.   
  23. listen(Listen, 5);  
  24.   
  25. SocketArray[EventTotal] = Listen;  
  26. EventArray[EventTotal] = NewEvent;  
  27. EventTotal++;  
  28.   
  29. while(TRUE)  
  30. {  
  31.     // Wait for network events on all sockets  
  32.     Index = WSAWaitForMultipleEvents(EventTotal,  
  33.         EventArray, FALSE, WSA_INFINITE, FALSE);  
  34.     Index = Index - WSA_WAIT_EVENT_0;  
  35.   
  36.     // Iterate through all events to see if more than one is signaled  
  37.     for(i=Index; i < EventTotal ;i++  
  38.     {  
  39.      Index = WSAWaitForMultipleEvents(1, &EventArray[i], TRUE, 1000,   
  40.       FALSE);  
  41.      if ((Index == WSA_WAIT_FAILED) ││ (Index == WSA_WAIT_TIMEOUT))  
  42.          continue;  
  43.      else  
  44.      {  
  45.          Index = i;  
  46.          WSAEnumNetworkEvents(  
  47.              SocketArray[Index],  
  48.              EventArray[Index],   
  49.             &NetworkEvents);  
  50.   
  51.          // Check for FD_ACCEPT messages       
  52.          if (NetworkEvents.lNetworkEvents & FD_ACCEPT)  
  53.          {   
  54.              if (NetworkEvents.iErrorCode[FD_ACCEPT_BIT] != 0)  
  55.              {  
  56.                  printf("FD_ACCEPT failed with error %d\n",   
  57.                      NetworkEvents.iErrorCode[FD_ACCEPT_BIT]);  
  58.                  break;  
  59.              }  
  60.   
  61.              // Accept a new connection, and add it to the  
  62.              // socket and event lists  
  63.              Accept = accept(  
  64.                  SocketArray[Index],  
  65.                  NULL, NULL);  
  66.   
  67.              // We cannot process more than   
  68.              // WSA_MAXIMUM_WAIT_EVENTS sockets, so close  
  69.              // the accepted socket  
  70.              if (EventTotal > WSA_MAXIMUM_WAIT_EVENTS)  
  71.              {  
  72.                  printf("Too many connections");  
  73.                  closesocket(Accept);  
  74.                  break;  
  75.              }  
  76.   
  77.              NewEvent = WSACreateEvent();  
  78.   
  79.              WSAEventSelect(Accept, NewEvent,  
  80.                  FD_READ │ FD_WRITE │ FD_CLOSE);  
  81.   
  82.              EventArray[EventTotal] = NewEvent;  
  83.              SocketArray[EventTotal] = Accept;  
  84.              EventTotal++;  
  85.              printf("Socket %d connected\n", Accept);  
  86.          }  
  87.   
  88.          // Process FD_READ notification  
  89.          if (NetworkEvents.lNetworkEvents & FD_READ)  
  90.          {  
  91.              if (NetworkEvents.iErrorCode[FD_READ_BIT] != 0)  
  92.              {  
  93.                  printf("FD_READ failed with error %d\n",   
  94.                      NetworkEvents.iErrorCode[FD_READ_BIT]);  
  95.                  break;  
  96.              }  
  97.   
  98.              // Read data from the socket  
  99.              recv(SocketArray[Index - WSA_WAIT_EVENT_0],  
  100.                  buffer, sizeof(buffer), 0);  
  101.          }  
  102.   
  103.          // Process FD_WRITE notification  
  104.          if (NetworkEvents.lNetworkEvents & FD_WRITE)  
  105.          {  
  106.              if (NetworkEvents.iErrorCode[FD_WRITE_BIT] != 0)  
  107.              {  
  108.                  printf("FD_WRITE failed with error %d\n",   
  109.                      NetworkEvents.iErrorCode[FD_WRITE_BIT]);  
  110.                  break;  
  111.              }  
  112.   
  113.              send(SocketArray[Index - WSA_WAIT_EVENT_0],  
  114.                 buffer, sizeof(buffer), 0);  
  115.             }  
  116.   
  117.             if (NetworkEvents.lNetworkEvents & FD_CLOSE)  
  118.             {  
  119.                 if (NetworkEvents.iErrorCode[FD_CLOSE_BIT] != 0)  
  120.                 {  
  121.                     printf("FD_CLOSE failed with error %d\n",   
  122.                         NetworkEvents.iErrorCode[FD_CLOSE_BIT]);  
  123.                     break;  
  124.                 }  
  125.   
  126.                 closesocket(SocketArray[Index]);  
  127.   
  128.                 // Remove socket and associated event from  
  129.                 // the Socket and Event arrays and decrement  
  130.                 // EventTotal  
  131.                 CompressArrays(EventArray, SocketArray, &EventTotal);  
  132.             }  
  133.             }  
  134.     }  
  135. }  
The WSAEventSelect model offers several advantages. It is conceptually simple and it does not require a windowed environment. The only drawback is its limitation of waiting on only 64 events at a time, which necessitates managing a thread pool when dealing with many sockets. Also, because many threads are required to handle a large number of socket connections, this model does not scale as well as the overlapped models discussed next.

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!