sockets - 使用GetQueuedCompletionStatus和ERROR_MORE_DATA的套接字

标签 sockets winsock io-completion-ports

我正在尝试将WinQueues与GetQueuedCompletionStatus一起使用,但似乎无法正确处理。步骤如下:

void foo() {
    ...
    SOCKET sck = WSASocket(AF_INET, SOCK_DGRAM, 0, 
        NULL, 0, WSA_FLAG_OVERLAPPED);
    ....
    bind(sck,(struct sockaddr *)&addr,sizeof(struct sockaddr_in));
    HANDLE hPort = CreateIoCompletionPort((HANDLE)sck, NULL, 0, 0 );
    OVERLAPPED pOverlapped = {0,};
    WSARecvFrom(sck,NULL,0,NULL,NULL,(struct sockaddr *)&laddr,&lsize,&pOverlapped,0);
    BOOL bReturn = GetQueuedCompletionStatus(
            hPort,
            &rbytes,
            (LPDWORD)&lpContext,
            &pOutOverlapped,
            INFINITE);
    ...
}


然后,我从外部工具将一些网络数据发送到绑定的端口。 GetQueuedCompletionStatus返回FALSE,而GetLastError()返回ERROR_MORE_DATA,这听起来是正确的,因为我没有在WSARecvFrom中提供缓冲区。

问题是我如何提供一个缓冲区来从失败的I / O操作中实际获取数据?

我尝试发出具有原始重叠结构的WSARecvFrom,但是它只是将另一个读取排队,并且直到发送更多网络数据后,对GetQueuedCompletionStatus的后续调用才返回。

在没有重叠结构的情况下调用WSARecvFrom将阻止它,并且在发送更多网络数据之前也不会返回。

因此,如何正确处理ERROR_MORE_DATA,而又不会丢失第一个操作中的数据?

最佳答案

就像任何读取操作一样,无论是否使用IOCP,都必须为WSARecvFrom()提供一个缓冲区。您必须确保缓冲区在内存中保持有效,直到IOCP操作完成为止。 IOCP将填充您提供的缓冲区,然后在完成时通知完成端口。

UDP在单个数据报中不能传输超过65535个字节,因此您可以将其用作最大缓冲区大小。

在您的示例中,您的代码被编写为同步运行(完全违背了使用IOCP的目的),因此您可以使用本地缓冲区:

void foo() {
    ...
    SOCKET sck = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    if (sck == INVALID_SOCKET)
    {
        // error, do something...
        return;
    }
    ....
    bind(sck,(struct sockaddr *)&addr, sizeof(struct sockaddr_in));
    HANDLE hPort = CreateIoCompletionPort((HANDLE)sck, NULL, 0, 0 );
    if (!hPort)
    {
        // error, do something...
        return;
    }

    WSAOVERLAPPED Overlapped = {0};
    Overlapped.hEvent = WSACreateEvent();

    BYTE buffer[0xFFFF];
    DWORD dwBytesRecvd = 0;
    DWORD dwFlags = 0;
    sockaddr_in fromaddr = {0};
    int fromaddrlen = sizeof(fromaddr);

    WSABUF buf;
    buf.len = sizeof(buffer);
    buf.buf = buffer;

    int iRet = WSARecvFrom(sck, &buf, 1, &dwBytesRecvd, &dwFlags, (sockaddr*)&fromaddr, &fromaddrlen, &Overlapped, NULL);
    if (iRet == SOCKET_ERROR)
    {
        if (WSAGetLastError() != WSA_IO_PENDING)
        {
           // error, do something...
           return;
        }

        DWORD rBytes;
        ULONG_PTR key;
        LPOVERLAPPED pOverlapped = NULL;

        if (!GetQueuedCompletionStatus(hPort, &rbytes, &key, &pOverlapped, INFINITE))
        {
            if (pOverlapped)
            {
                // WSARecvFrom() failed...
            }
            else
            {
                // GetQueuedCompletionStatus() failed...
            }

            // do something...
            return;
        }
    }

    // I/O complete, use buffer, dwBytesRecvd, dwFlags, and fromaddr as needed...
}


但是,这违背了IOCP的宗旨。如果您确实想同步,则可以只使用recvfrom()并让它阻塞调用线程,直到数据到达为止。当您有一个为完成端口服务的线程池时,IOCP效果最佳。调用WSARecvFrom()并使其在后台运行,请勿等待。让一个单独的线程调用GetQueuedCompletionPort()并在接收到数据时对其进行处理,例如:

struct MyOverlapped
{
    WSAOVERLAPPED overlapped;
    BYTE buffer[0xFFFF];
    DWORD buflen;
    DWORD flags;
    sockaddr_storage fromaddr;
    int fromaddrLen;
};

HANDLE hPort = NULL;

void foo() {
    ...
    SOCKET sck = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    if (sck == INVALID_SOCKET)
    {
        // error, do something...
        return;
    }
    ....
    bind(sck,(struct sockaddr *)&addr, sizeof(struct sockaddr_in));
    hPort = CreateIoCompletionPort((HANDLE)sck, NULL, 0, 0 );
    if (!hPort)
    {
        // error, do something...
        return;
    }

    MyOverlapped *ov = new MyOverlapped;
    ZeroMemory(ov, sizeof(*ov));
    ov->overlapped.hEvent = WSACreateEvent();
    ov->fromaddrlen = sizeof(ov->fromaddr);

    WSABUF buf;
    buf.len = sizeof(ov->buffer);
    buf.buf = ov->buffer;

    int iRet = WSARecvFrom(sck, &buf, 1, &ov->buflen, &ov->flags, (sockaddr*)&ov->fromaddr, &ov->fromaddrlen, (WSAOVERLAPPED*)ov, NULL);
    if (iRet == SOCKET_ERROR)
    {
        if (WSAGetLastError() != WSA_IO_PENDING)
        {
           // error, do something...
           return;
        }

        // WSARecvFrom() is now operating in the background,
        // the IOCP port will be signaled when finished...
    }
    else
    {
        // data is already available,
        // the IOCP port will be signaled immediately...
    }

    ...
}

...

// in another thread...

{
    ...

    DWORD rbytes;
    ULONG_PTR key;
    MyOverlapped *ov = NULL;

    if (!GetQueuedCompletionStatus(hPort, &rbytes, &key, (LPOVERLAPPED*)&ov, INFINITE))
    {
        if (ov)
        {
            // WSARecvFrom() failed...
            // free ov, or reuse it for another operation...
        }
        else
        {
            // GetQueuedCompletionStatus() failed...
        }
    }
    else
    {
        // use ov as needed...
        // free ov, or reuse it for another operation...
    }

    ...
}

关于sockets - 使用GetQueuedCompletionStatus和ERROR_MORE_DATA的套接字,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31883438/

相关文章:

java - ObjectInputStream/ObjectOutputStream 工作不正常

c++ - 将我简单的 winsock 服务器/客户端应用程序转变为简单的网络服务器

c++ - sendto 和 recvfrom 在同一个程序中?

C++:#including <thread> 重新定义 winsock 函数 bind(...)?

c - UDP 数据包在套接字上停留多长时间?

C#并行IO完成端口

asynchronous - Mac OS X 的 IO 完成端口

使用 ConfigureAwait(false) 的 C# async/await 链接

c++ - WSARecv 的 dsBytes、dwFlags 参数

php - 在实时场景中使用 Node JS 和 PHP