程式扎記: [ NP in MS ] Winsock I/O Methods (Part1 : Socket Modes)

標籤

2011年4月30日 星期六

[ NP in MS ] Winsock I/O Methods (Part1 : Socket Modes)


Preface :
As we mentioned, Windows sockets perform I/O operations in two socket operating modes : blocking and non-blocking. In blocking mode, Winsock calls that perform I/O—such as send and recv—wait until the operation is complete before they return to the program. In non-blocking mode, the Winsock functions return immediately. Applications running on the Windows CE and Windows 95 (with Winsock 1) platforms, which support few of the I/O models, require you to take certain steps with blocking and non-blocking sockets to handle a variety of situations.

Blocking Mode :
Blocking sockets cause concern because any Winsock API call on a blocking socket can do just that—block for some period of time. Most Winsock applications follow a producer-consumer model in which the application reads (or writes) a specified number of bytes and performs some computation on that data. The following code snippet illustrates this model :
  1. SOCKET  sock;  
  2. char    buff[256];  
  3. int     done = 0,  
  4.         nBytes;  
  5.   
  6. ...  
  7.   
  8. while(!done)  
  9. {  
  10.     nBytes = recv(sock, buff, 65);  
  11.     if (nBytes == SOCKET_ERROR)  
  12.      {  
  13.         printf("recv failed with error %d\n",  
  14.             WSAGetLastError());  
  15.         Return;  
  16.     }  
  17.     DoComputationOnData(buff);  
  18. }  
  19.   
  20. ...  
The problem with this code is that the recv function might never return if no data is pending because the statement says to return only after reading some bytes from the system's input buffer. Some programmers might be tempted to peek for the necessary number of bytes in the system's buffer by using theMSG_PEEK flag in recv or by calling ioctlsocket with the FIONREAD option. Peeking for data without actually reading it is considered bad programming practice and should be avoided at all costs (reading the data actually removes it from the system's buffer). The overhead associated with peeking is great because one or more system calls are necessary just to check the number of bytes available. Then, of course, there is the overhead of making the recv call that removes the data from the system buffer. To avoid this, you need to prevent the application from totally freezing because of lack of data (either from network problems or from client problems) without continually peeking at the system network buffers. One method is to separate the application into a reading thread and a computation thread. Both threads share a common data buffer. Access to this buffer is protected with a synchronization object, such as an event or a mutex. The purpose of the reading thread is to continually read data from the network and place it in the shared buffer. When the reading thread has read the minimum amount of data necessary for the computation thread to do its work, it can signal an event that notifies the computation thread to begin. The computation thread then removes a chunk of data from the buffer and performs the necessary calculations.
The following section of code illustrates this approach by providing two functions: one responsible for reading network data (ReadThread) and one for performing the computations on the data (ProcessThread) :
  1. #define MAX_BUFFER_SIZE    4096  
  2. // Initialize critical section (data) and create   
  3. // an auto-reset event (hEvent) before creating the  
  4. // two threads  
  5. CRITICAL_SECTION data;  
  6. HANDLE           hEvent;  
  7.   
  8. SOCKET           sock;  
  9. TCHAR            buff[MAX_BUFFER_SIZE];  
  10. int              done=0;  
  11.   
  12.   
  13. // Create and connect sock  
  14. ...  
  15. // Reader thread  
  16. void ReadThread(void)   
  17. {  
  18.     int nTotal = 0,  
  19.         nRead = 0,  
  20.         nLeft = 0,  
  21.         nBytes = 0;  
  22.   
  23.     while (!done)     
  24.     {  
  25.         nTotal = 0;  
  26.         nLeft = NUM_BYTES_REQUIRED;     
  27.           
  28. // However many bytes constitutes   
  29. // enough data for processing   
  30. // (i.e. non-zero)  
  31.         while (nTotal != NUM_BYTES_REQUIRED)   
  32.         {  
  33.             EnterCriticalSection(&data);  
  34.             nRead = recv(sock, &(buff[MAX_BUFFER_SIZE - nBytes]),  
  35.                 nLeft, 0);  
  36.             if (nRead == -1)  
  37.             {  
  38.                 printf("error\n");  
  39.                 ExitThread();  
  40.             }  
  41.             nTotal += nRead;  
  42.             nLeft -= nRead;  
  43.   
  44.             nBytes += nRead;  
  45.             LeaveCriticalSection(&data);  
  46.         }  
  47.         SetEvent(hEvent);  
  48.     }  
  49. }  
  50.   
  51. // Computation thread  
  52. void ProcessThread(void)   
  53. {  
  54.     WaitForSingleObject(hEvent);  
  55.   
  56.     EnterCriticalSection(&data);  
  57.     DoSomeComputationOnData(buff);  
  58.       
  59.     // Remove the processed data from the input  
  60.     // buffer, and shift the remaining data to  
  61.     // the start of the array  
  62.     nBytes -= NUM_BYTES_REQUIRED;  
  63.   
  64.     LeaveCriticalSection(&data);  
  65. }  
One drawback of blocking sockets is that communicating via more than one connected socket at a time becomes difficult for the application. Using the foregoing scheme, the application could be modified to have a reading thread and a data processing thread per connected socket. This adds quite a bit of housekeeping overhead, but it is a feasible solution. The only drawback is that the solution does not scale well once you start dealing with a large number of sockets.

Non-blocking Mode :
The alternative to blocking sockets is non-blocking sockets. Non-blocking sockets are a bit more challenging to use, but they are every bit as powerful as blocking sockets, with a few advantages. The following example illustrates how to create a socket and put it into non-blocking mode :
  1. SOCKET        s;  
  2. unsigned long ul = 1;  
  3. int           nRet;  
  4.   
  5. s = socket(AF_INET, SOCK_STREAM, 0);  
  6. nRet = ioctlsocket(s, FIONBIO, (unsigned long *) &ul);  
  7. if (nRet == SOCKET_ERROR)  
  8. {  
  9.     // Failed to put the socket into non-blocking mode  
  10. }  
Once a socket is placed in non-blocking mode, Winsock API calls that deal with sending and receiving data or connection management return immediately. In most cases, these calls fail with the error WSAEWOULDBLOCK, which means that the requested operation did not have time to complete during the call. For example, a call to recv returns WSAEWOULDBLOCK if no data is pending in the system's input buffer. Often additional calls to the same function are required until it encounters a successful return code. Table 5-2 describes the meaning of WSAEWOULDBLOCK when returned by commonly used Winsock calls.

Because non-blocking calls frequently fail with the WSAEWOULDBLOCK error, you should check all return codes and be prepared for failure at any time. The trap many programmers fall into is that of continually calling a function until it returns a success. For example, placing a call to recv in a tight loop to read 200 bytes of data is no better than polling a blocking socket with the MSG_PEEK flag mentioned previously. Winsock's socket I/O models can help an application determine when a socket is available for reading and writing.
Each socket mode—blocking and non-blocking—has advantages and disadvantages. Blocking sockets are easier to use from a conceptual standpoint but become difficult to manage when dealing with multiple connected sockets or when data is sent and received in varying amounts and at arbitrary times. On the other hand, non-blocking sockets are more difficult because more code needs to be written to handle the possibility of receiving a WSAEWOULDBLOCKerror on every Winsock call. Socket I/O models help applications manage communications on one or more sockets at a time in an asynchronous fashion.
This message was edited 5 times. Last update was at 20/04/2011 14:06:03

沒有留言:

張貼留言

網誌存檔

關於我自己

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