Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IO Completion ports: How does WSARecv() work?

Tags:

c++

iocp

winsock

I want to write a server using a pool of worker threads and an IO completion port. The server should processes and forwards messages between multiple clients. The 'per client' data is in a class ClientContext. Data between instances of this class are exchanged using the worker threads. I think this is a typical scenario.

However, I have two problems with those IO completion ports.

(1) The first problem is that the server basically receives data from clients but I never know if a complete message was received. In fact WSAGetLastError() always returns that WSARecv() is still pending. I tried to wait for the event OVERLAPPED.hEvent with WaitForMultipleObjects(). However, it blocks forever, i.e WSARecv() never completes in my program. My goal is to be absolutely sure that the whole message has been received before further processing starts. My message has a 'message length' field in its header, but I don't really see how to use it with the IOCP function parameters.

(2) If WSARecv() is commented out in the code snippet below, the program still receives data. What does that mean? Does it mean that I don't need to call WSARecv() at all? I am not able to get a deterministic behaviour with those IO completion ports. Thanks for your help!

while(WaitForSingleObject(module_com->m_shutdown_event, 0)!= WAIT_OBJECT_0)
{

    dequeue_result = GetQueuedCompletionStatus(module_com->m_h_io_completion_port,
                                               &transfered_bytes,
                                               (LPDWORD)&lp_completion_key,
                                               &p_ol,
                                               INFINITE);
     if (lp_completion_key == NULL)
     {
         //Shutting down
         break;
     }

     //Get client context
     current_context = (ClientContext *)lp_completion_key;

     //IOCP error
     if(dequeue_result == FALSE)
     {
         //... do some error handling...
     }
     else
     {   
         // 'per client' data
         thread_state = current_context->GetState();
         wsa_recv_buf = current_context->GetWSABUFPtr();

         // 'per call' data
         this_overlapped = current_context->GetOVERLAPPEDPtr();
     }

     while(thread_state != STATE_DONE)
     {
         switch(thread_state)
         {
         case STATE_INIT:

             //Check if completion packet has been posted by internal function or by WSARecv(), WSASend()
             if(transfered_bytes > 0)
             {
                 dwFlags = 0;
                 transf_now = 0;
                 transf_result = WSARecv(current_context->GetSocket(),
                                         wsa_recv_buf,
                                         1,
                                         &transf_now,
                                         &dwFlags,
                                         this_overlapped,
                                         NULL);

                 if (SOCKET_ERROR == transf_result && WSAGetLastError() != WSA_IO_PENDING)
                 {   
                     //...error handling...
                     break;
                 }

                 // put received message into a message queue

             }
             else // (transfered_bytes == 0)
             {
                 // Another context passed data to this context
                 // and notified it via PostQueuedCompletionStatus().
             }
             break;
         }
     }
 }
like image 989
Olliwaa Avatar asked Mar 01 '23 00:03

Olliwaa


1 Answers

(1) The first problem is that the server basically receives data from clients but I never know if a complete message was received.

Your recv calls can return anywhere from 1 byte to the whole 'message'. You need to include logic that works out when it has enough data to work out the length of the complete 'message' and then work out when you actually have a complete 'message'. Whilst you do NOT have enough data you can reissue a recv call using the same memory buffer but with an updated WSABUF structure that points to the end of the data that you have already recvd. In that way you can accumulate a full message in your buffer without needing to copy data after every recv call completes.

(2) If WSARecv() is commented out in the code snippet below, the program still receives data. What does that mean? Does it mean that I don't need to call WSARecv() at all?

I expect it just means you have a bug in your code...

Note that it's 'better' from a scalability point of view not to use the event in the overlapped structure and instead to associate the socket with the IOCP and allow the completions to be posted to a thread pool that deals with your completions.

I have a free IOCP client/server framework available from here which may give you some hints; and a series of articles on CodeProject (first one is here: http://www.codeproject.com/KB/IP/jbsocketserver1.aspx) where I deal with the whole 'reading complete messages' problem (see "Chunking the byte stream").

like image 107
Len Holgate Avatar answered Mar 06 '23 21:03

Len Holgate