我是初学IOCP,有好些问题还不明白,问题如下:
1.在listen到新的连接后,需要再次调用CreateIoCompletionPort,之后还要要提请一次收或发的操作,我不明白这一步是否必需,那到底是用WSARecv还是WSASend。2.假设客户端连上后发出了2次int型数据,那这两次数据是是如何被接收下来的?是被CreateIoCompletionPort后发起的WSARecv接收还是在GetQueuedCompletionStatus之后再调用WSARecv接收?3.在许多程序的完成键结构中都定义了操作类型参数(opType),表明是Recv还是Send,可是我不明白系统怎么会知道在GetQueuedCompletionStatus后就会自动认得opType是表明操作的参数,并给其赋与相应的值。

解决方案 »

  1.   

    http://community.csdn.net/Expert/topic/3056/3056877.xml?temp=.9534876这是我以前发的讨论贴,希望对你有帮助
      

  2.   

    你可以把完成端口看成后台的一个调度的东东,当绑定在它上面的句柄有IO完成操作是它就会唤醒一个调用了GetQueuedCompletionStatus的线程。
    1、调用一次收或者发的操作是用OVERLAP方式进行,所以可以立即退出接收,至于接收或者发送的过程就交给系统了,当接收或者发送完成自然会唤醒一个线程(该线程已经调用了GetQueuedCompletionStatus函数)。如果不知道对方什么时候要发下一包数据就调用WSARecv,这样当对方发送数据时自然有一个线程会响应
    2、第一包数据肯定是被第一次发起的WSARecv接收下来的,但是一次WSARecv不能保证数据全部接收,一般对于数据流还要定义数据格式,然后通过检查接收数据的格式来看有没有接收完,没有接收完在GetQueuedCompletionStatus后继续接收
    3、opType是自定义的,你在WSASend或WSARecv之前赋值,系统并不管,在发送和接收的IO操作完成之后完成端口都会唤醒一个线程,它并不区分是发送还是接收,所以需要你自己通过这个值来区分上次是调用了WSARecv还是WSASend。
      

  3.   

    1、用 PostQueuedCompletionStatus()。
    2、如果第 1 步用 PostQueuedCompletionStatus() 的话就不存在这个问题,当然是 GetQueuedCompletionStatus 后的 WSARecv()。
    3、同意楼上的。
      

  4.   

    to needways(needways):
    PostQueuedCompletionStatus好像是在停止完成端口时才使用的啊?
      

  5.   

    if (m_hIOPort != CreateIoCompletionPort((HANDLE)sock, m_hIOPort,
    (DWORD)hUser, 0))
    {
    InterlockedDecrement(&m_nCurrentUsers);
    closesocket(sock); // 无法分配完成端口,将新创建的放入空闲列表
    EnterCriticalSection(&m_FreeLocker);
    hUser->socket = INVALID_SOCKET;
    hUser->pNextIOCP = m_FreeLister;
    m_FreeLister = hUser;
    LeaveCriticalSection(&m_FreeLocker); return;
    } // 添加到用户列表
    EnterCriticalSection(&m_UsedLocker);
    if (m_UsedLister != NULL) {
    m_UsedLister->pPrevIOCP = hUser;
    }
    hUser->pNextIOCP = m_UsedLister;
    hUser->pPrevIOCP = NULL;
    m_UsedLister = hUser;
    LeaveCriticalSection(&m_UsedLocker); // 通知线程池
    POVERLAPPEDPLUS pOverlap = CreateOverlappedPlus(IOAccept);
    if (pOverlap == NULL) { OnFDClose(hUser, NULL); return; }
    BOOL bSuccess = PostQueuedCompletionStatus(m_hIOPort, 0,
    (DWORD)hUser, &pOverlap->m_ol);
    if ((!bSuccess && GetLastError( ) != ERROR_IO_PENDING)) {
    OnFDClose(hUser, NULL);
    DestroyOverlappedPlus(pOverlap);
        return;
        }
      

  6.   

    另外在完成键的结构中经常会有如下定义
      WSABUF DataBuf;
      CHAR   Buffer[DATA_BUFSIZE];
    这两个有什么区别和用处吗?ZeroMemory(&(PerIoData->Overlapped),sizeof(OVERLAPPED));
    PerIoData->DataBuf.len = DATA_BUFFER_LEN;
    PerIoData->DataBuf.buf = PerIoData->Buffer;//不明白这一句,PerIoData->Buffer中是什
                                               //么?
    PerIoData->OperationType = RECV_POSTED;WSARecv(PerHandleData->Socket,
    &(PerIoData->DataBuf),1,&RecvBytes,
    &Flags,&(PerIoData->Overlapped),NULL);
      

  7.   

    因为 WSARecv 并不一定能立即接收到,需要一个缓冲,让它接收到数据后,放在这个缓冲里面。
      

  8.   

    喔!我明白PerIoData->DataBuf.buf = PerIoData->Buffer;这句的意思了,就是为了继续收完。我试一试先,谢谢 needways(needways),再看看可能还有问题请教:)
      

  9.   

    如果Client连发3次:
    int cmd = 120;
    int ID=100;
    int port=110;
    m_pClientSocket->Send(&cmd,sizeof(int));
    m_pClientSocket->Send(&ID,sizeof(int));
    m_pClientSocket->Send(&port,sizeof(int));那server端的接收应该怎么写呢?
    while(1)
    {
      GetQueuedCompletionStatus(CompletionPort, &BytesTransferred,
           (LPDWORD)&PerHandleData, (LPOVERLAPPED *) &PerIoData,  INFINITE); ......if(PerIoData->OpType == otRecv){
      //怎么把剩下的2个接收下来?是调用两次WSARecv吗?我试了,好像不对!
    }
    }
      

  10.   

    调用 1 次,调用完后,又进入 GetQueuedCompletionStatus 等待下一个数据包接收完毕,收完后又调用 WSARecv,然后再次进入 GetQueuedCompletionStatus[就是一个循环接收啦],等待下一个数据饣。
      

  11.   

    我写了如下接收代码:可是发现每次Client发3次(发送代码如上),server只能收到前2个数据(即AfxMessageBox弹出2次,分别显示为120和100),不知为什么?if(PerIoData->OpType == otRecv){
      AfxMessageBox(PerIoData->DataBuf.buf);

      PerIoData->BytesRECV = BytesTransferred;  Flags = 0;
      ZeroMemory(&(PerIoData->Overlapped),sizeof(OVERLAPPED));
      PerIoData->DataBuf.len = DATA_BUFSIZE;
      PerIoData->DataBuf.buf = PerIoData->Buffer;
      PerIoData->OpType = otRecv;  WSARecv(PerHandleData->Socket,&(PerIoData->DataBuf),1,&RecvBytes,
            &Flags,&(PerIoData->Overlapped),NULL);}
      

  12.   

    有没有可能是 WSARecv 将最后两个包作一个收了?
      

  13.   

    to :"有没有可能是 WSARecv 将最后两个包作一个收了?"
    不会吧,不可能每次都是这样啊?
      

  14.   

    那就是你提前退出 while 循环了。
      

  15.   

    这是我的接收代码:
    if(PerIoData->OpType == otRecv){
      char* c=PerIoData->DataBuf.buf;
      CIOCPtestApp *pApp = (CIOCPtestApp*)AfxGetApp();
      ::SendMessage(pApp->m_DlghWnd,WM_RECV_DATA,0,(LPARAM)c);

      PerIoData->BytesRECV = BytesTransferred;  Flags = 0;
      ZeroMemory(&(PerIoData->Overlapped),sizeof(OVERLAPPED));
      PerIoData->DataBuf.len = DATA_BUFSIZE;
      PerIoData->DataBuf.buf = PerIoData->Buffer;
      PerIoData->OpType = otRecv;
      WSARecv(PerHandleData->Socket,&(PerIoData->DataBuf),1,&RecvBytes,
            &Flags,&(PerIoData->Overlapped),NULL);}
    为什么不管client发几个数据包,server只能收到前2个数据包,后面都收不到?
      

  16.   

    将代码给我看看。我帮你改
    [email protected]
      

  17.   

    谢谢needways(needways),已经发了,请查收。
      

  18.   

    在 Send() 之间加一个 Sleep(1),就可以了。
      

  19.   

    Sleep(1)... 这办法也教出来了...IOCP,把介绍的文章读许多遍,就可以懂了.比如30遍.
      

  20.   

    Sleep(1)确实可以,不过似乎是降低了系统的效率啊?因该说这是治表不治本,不过还是非常感谢 needways(needways),劳驾再看看怎么在server端作文章,好吗?
      

  21.   

    如果不用 Sleep() 的话,100、110 就作一次收了。你有没有注意到最后一次 PerIoData->DataBuf.len 是 8,而不是 4。
      

  22.   

    哦。错了,不是 PerIoData->DataBuf.len 而是 BytesTransferred。
      

  23.   

    不好意思,昨天下午公司停电了,没法回复。
    不过这个问题我已经解决了,确实如你所说后两个数据被一次收了下来。我现在的解决办法如下:
    if(PerIoData->OpType == otRecv)
    {
      char* c=PerIoData->Buffer;
      CIOCPtestApp *pApp = (CIOCPtestApp*)AfxGetApp();

      PerIoData->BytesRECV = BytesTransferred;  int i=0;
      while(BytesTransferred>0){//将收到的数据全部处理掉
       CopyMemory(c,PerIoData->Buffer+4*i,4);
       ::SendMessage(pApp->m_DlghWnd,WM_RECV_DATA,0,(LPARAM)c);
       BytesTransferred -= 4;
       i++;
      }

      Flags = 0;
      ZeroMemory(&(PerIoData->Overlapped),sizeof(OVERLAPPED));
      PerIoData->DataBuf.len = DATA_BUFSIZE;
      PerIoData->DataBuf.buf = PerIoData->Buffer;
      PerIoData->OpType = otRecv;  if(WSARecv(PerHandleData->Socket,&(PerIoData->DataBuf),1,&RecvBytes,
         &Flags,&(PerIoData->Overlapped),NULL) == SOCKET_ERROR)
      {
    if(WSAGetLastError() != ERROR_IO_PENDING){
      CString s;
      s.Format("WSARecv() failed with error %d\n", WSAGetLastError());
      AfxMessageBox(s);
      return 0;
    }
      }
    }不过这样给我的处理带来一些不便,另外我发现这样在接收int型的数据时有问题,数字太大了接收时就不对了,超过256,接收端就等于cmd%256?怎么解决啊?
      

  24.   

    建议您在发送的数据包里面包含数据包的大小。数字太大时接收端不正确,可作如下修改。// 接收时
     ... int val;
     int i=0;
     while(BytesTransferred>0){//将收到的数据全部处理掉
       val = *(int*)(PerIoData->Buffer + 4 * i);
       // 不要用 SendMessage,改为用 PostMessage
       // ::SendMessage(pApp->m_DlghWnd,WM_RECV_DATA,0,(LPARAM)c);
       ::PostMessage(pApp->m_DlghWnd, WM_RECV_DATA, 0, (LPARAM)val);
       BytesTransferred -= 4;
       i++;
    }
     ...// 显示时
      CString str;
      str.Format("%d", lParam);
      ListBox_AddString(str);