【问题标题】:Why does WSASend return 0 but still call the completion routine?为什么 WSASend 返回 0 但仍然调用完成例程?
【发布时间】:2014-04-21 10:58:50
【问题描述】:

文档清楚地表明,如果 WSASend 立即完成,您将获得 WSA_IO_PENDING,但这永远不会发生。我总是得到 0,并且 dwBytesTransferred 总是与我发送的字节匹配。但是,似乎有时会调用我的完成例程,有时则不会。我为发送分配了缓冲区,所以如果不调用完成例程,我需要释放缓冲区。

我有三个即兴计数器,m_dwAsyncSend、m_dwSyncSend 和 m_dwCompletions。 m_dwAsycSend 始终为零,并且 m_dwSyncSend 和 m_dwCompletions 始终相距甚远,因为一个 m_dwSyncSend 可能是 750 而 m_dwCompletions 是 2。线程有很多时候是可警告的,所以我认为我不会那样饿死它。

这让我发疯了!现在是午夜之后,我整天都在这。如果其中任何一个不连贯,我会责备它!

这是代码,我认为您不需要类文件来查看我在做什么。

void
CALLBACK
SendCompletion(
    DWORD dwError,
    DWORD cbTransferred,
    LPOVERLAPPED pOvl,
    DWORD dwFlags
    )
{
    LPWSABUF pBuffer = (LPWSABUF)((DWORD_PTR)pOvl + sizeof(OVERLAPPED));
    CNetAsyncSocket *pSock = (CNetAsyncSocket *)pOvl->hEvent;
    pSock->m_dwCompletions++;
    if(dwError != NO_ERROR)
    {
        // If things didn't go as planned, ring the bell and disconnect.
        pSock->Disconnect();
        tracef(TT_REGULAR, 1,
            "SOCKET_ERROR in CNetAsyncSocket::Send(), disconnecting, error code: %ld, on socket: %s:%ld",
            dwError, pSock->GetIP(), pSock->GetPort());
        free(pOvl);
    }
    else
    {
        // If we sent less than we meant to, queue up the rest.
        if(cbTransferred < pBuffer->len)
        {
            DWORD dwRemaining = pBuffer->len - cbTransferred;
            memmove(pBuffer->buf, (PVOID)((DWORD_PTR)pBuffer->buf + dwRemaining), dwRemaining);
            pBuffer->len = dwRemaining;
        }
        else
        {
            free(pOvl);
        }
    }
}
void CNetAsyncSocket::SendAsync(PBYTE pData, DWORD dwLength)
{
    // We want to minimize heap churn, so let's do this in one allocation.
    // Also, having this in one chunk of memory makes it easier to interpret
    // it on the other side.
    DWORD dwAllocSize =
        sizeof(OVERLAPPED) +        // The OVERLAPPED struct.
        sizeof(WSABUF) +            // The buffer info.
        dwLength;                   // The actual buffer we're copying.
    LPOVERLAPPED pOvl = (LPOVERLAPPED)malloc(dwAllocSize);

    if(pOvl == NULL)
    {
        // Out of memory.
    }

    // Initialize the allocation.
    ZeroMemory(pOvl, dwAllocSize);


    // Build the pointers.
    LPWSABUF pBuffer = (LPWSABUF)((DWORD_PTR)pOvl + sizeof(OVERLAPPED));
    pBuffer->len = dwLength;
    assert(pBuffer->len < 1000000);
    pBuffer->buf = (PCHAR)((DWORD_PTR)pBuffer + sizeof(WSABUF));
    // When you have a completion routine, the hEvent member is ignored, so we
    // can use it to pass our this pointer to the completion routine.
    pOvl->hEvent = (PVOID)this;

    // Copy the data to the buffer.
    CopyMemory(pBuffer->buf, pData, dwLength);

    // Send the data.
    DWORD dwSent = 0;
    int iResult = WSASend(
        m_hSocket,          // The socket.
        pBuffer,            // The WSABUF struct.
        1,                  // Number of structs (1).
        &dwSent,            // Bytes sent. Updated if it happens synchronously.
        0,                  // No flags.
        pOvl,               // The overlapped struct.
        SendCompletion);    // Completion routine.

    if(iResult == NO_ERROR)
    {
        // If the send happened synchronously, we can go ahead and delete the
        // memory that we allocated.

        // TODO: look at bytes transferred, and if they're less than the total
        // then issue another send with the remainder.

        if(HasOverlappedIoCompleted(pOvl))
        {
            // If I actually free this here, the completion routine gets garbage.
            //free(pOvl);
            m_dwSyncSend++;
        }
        else
        {
            m_dwAsyncSend++;
        }
    }
    else
    {
        // If we got WSA_IO_PENDING, then that just means the completion routine
        // will take care of it.
        if(iResult != WSA_IO_PENDING)
        {
            Disconnect();
            tracef(TT_REGULAR, 1,
                "SOCKET_ERROR in CNetAsyncSocket::Send(), disconnecting, error code: %ld, on socket: %s:%ld",
                iResult, GetIP(), GetPort());
            // Don't need the payload anymore.
            free(pOvl);
        }
        else
        {
            m_dwAsyncSend++;
        }
    }
}

【问题讨论】:

  • 文档说如果操作可以立即完成,您将获得 0,如果不能立即完成,您将获得 SOCKET_ERROR(最后一个错误代码为 WSA_IO_PENDING)。无论哪种方式,对完成例程的调用都应该排队。因此,您应该释放缓冲区的唯一情况是发生 WSA_IO_PENDING 以外的错误。我注意到您的代码当前不正确地检查返回值,但不清楚这将如何产生您描述的症状。您确定在创建套接字时设置了 WSA_FLAG_OVERLAPPED 吗?
  • 嗨,Harry,是的,当您睡眠不足时,您可能不应该这样做!我不知道为什么我认为我读到如果 I/O 同步发生就不会排队完成,但我错了。另外,我错了没有线程饥饿。添加 SleepEx 可以解决这个问题。另外,你是对的,我错误地检查了退货。当我得到 SOCKET_ERROR 时,我需要得到 WSAGetLastError() 而不是将返回值视为错误值。感谢您的回复!
  • 好的,太好了。我已将评论的相关部分提升为答案。 :-)

标签: c++ windows winsock winsock2 overlapped-io


【解决方案1】:

文档说如果操作可以立即完成,您将获得 0,如果不能立即完成,您将获得 SOCKET_ERROR(最后一个错误代码为 WSA_IO_PENDING)。无论哪种方式,对完成例程的调用都将排队。

因此,您所描述的行为符合预期,您应该释放缓冲区的唯一情况是发生 WSA_IO_PENDING 以外的错误。

【讨论】:

  • 这不能回答问题。我有完全相同的问题。昨天所有工人都很好。今天,第一个 WSASend 触发 SendCompletion 和所有其余的 - 不是。 iResult 在所有情况下都是相同的,等于0。所以问题仍然是:为什么重叠模式下的 WSASend 不会触发 SendCompletion
  • @htonus,这个问题的主要焦点是询问为什么在返回值为零时对完成例程的调用排队,这是预期的。这个问题也确实说这只是偶尔发生,但在 cmets 中,OP 确认这是他的一个错误:“添加对 SleepEx 的调用可以解决这个问题。”
  • @htonus,按原样,您链接到的代码在没有首先调用 WSARecv 的情况下永远不会调用 WSASend,您是在谈论设置 me-&gt;client 的情况吗?因为那里的问题是您没有在调用 WSASend 的同一线程上执行警报等待,所以完成例程被排队但从未被调用。
猜你喜欢
  • 2015-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-30
  • 2015-08-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多