IO完成端口:WSARecv()如何工作?

时间:2009-11-20 09:32:41

标签: c++ winsock iocp

我想使用工作线程池和IO完成端口编写服务器。服务器应该在多个客户端之间处理和转发消息。 “每个客户端”数据位于ClientContext类中。使用工作线程交换此类实例之间的数据。我认为这是典型的情况。

但是,我对这些IO完成端口有两个问题。

(1)第一个问题是服务器基本上从客户端接收数据,但我不知道是否收到了完整的消息。事实上,WSAGetLastError()始终返回WSARecv()仍处于挂起状态。我试图用WaitForMultipleObjects()等待事件OVERLAPPED.hEvent。然而,它永远阻止,即WSARecv()永远不会在我的程序中完成。 我的目标是在进一步处理开始之前完全确定已收到整条消息。我的消息在其标题中有一个“消息长度”字段,但我真的没有看到如何将它与IOCP函数参数一起使用。

(2)如果WSARecv()在下面的代码片段中被注释掉,程序仍会接收数据。那是什么意思?这是否意味着我根本不需要调用WSARecv()?我无法通过这些IO完成端口获得确定性行为。 谢谢你的帮助!

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;
         }
     }
 }

1 个答案:

答案 0 :(得分:8)

  

(1)第一个问题是   服务器基本上接收数据   客户,但我永远不知道是否完整   收到了消息。

您的recv调用可以从1个字节返回到整个“消息”。你需要包含逻辑,当它有足够的数据来计算完整的'消息'的长度,然后在你真正有一个完整的'消息'时解决。虽然您没有足够的数据,但您可以使用相同的内存缓冲区重新发出recv调用,但使用更新的WSABUF结构,该结构指向已经记录的数据的末尾。这样,您可以在缓冲区中累积完整的消息,而无需在每次recv调用完成后复制数据。

  

(2)如果注释掉了WSARecv()   下面的代码片段,程序   仍然收到数据。那是什么   意思?这是否意味着我不需要   完全调用WSARecv()?

我希望它只是意味着您的代码中存在错误...

请注意,从可伸缩性的角度来看,“更好”不要在重叠结构中使用事件,而是将套接字与IOCP相关联,并允许将完成发布到处理完成的线程池中。

我有here提供的免费IOCP客户端/服务器框架,可能会给你一些提示;以及关于CodeProject的一系列文章(第一篇是http://www.codeproject.com/KB/IP/jbsocketserver1.aspx),其中我处理整个“阅读完成消息”问题(参见“组织字节流”)。