【问题标题】:Sockets using GetQueuedCompletionStatus and ERROR_MORE_DATA使用 GetQueuedCompletionStatus 和 ERROR_MORE_DATA 的套接字
【发布时间】:2015-08-07 17:26:25
【问题描述】:

我正在尝试将 GetQueuedCompletionStatus 与 winsocks 一起使用,但我似乎无法正确使用它。程序如下:

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 而不丢失第一次操作的数据?

【问题讨论】:

  • 为什么不提供 WSABUF 缓冲区/bufferArray 调用?为什么要用可疑的论据挑起一个不可靠的数据报协议?
  • 我试图让 Windows 在实际读取数据之前告诉我何时有可用数据。
  • I/O 读取功能不能这样工作。无论读取是同步的还是异步的,您都必须为要填充的函数提供一个有效的缓冲区。要发现套接字上的数据何时可用而不实际读取它,您需要使用select()WSAAsyncSelect()WSAEventSelect() 来检测套接字何时可读(有数据待处理),然后您可以使用缓冲。在您的示例中,您尝试使用 IOCP,它在后台执行实际读取,然后在读取完成时通知您,因此您需要一个真正的缓冲区来读取它。

标签: sockets winsock io-completion-ports


【解决方案1】:

您必须为WSARecvFrom() 提供一个缓冲区,就像任何读取操作一样,无论您是否使用IOCP。您必须确保缓冲区在内存中保持有效,直到 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...
    }

    ...
}

【讨论】:

  • 所以,你说 IOCP 中的 ERROR_MORE_DATA 是无关紧要的(至少在套接字的情况下)。在这种情况下,如果输入缓冲区对于操作来说太小,数据是否会被丢弃?是不是绝对没有办法用新的缓冲区重做操作?您能否指出任何描述此行为的 Microsoft 文档?
  • ERROR_MORE_DATA 表示您提供的缓冲区小于接收到的数据。这是真的,因为您根本没有提供任何缓冲区(并且在 UDP 中有 0 长度数据报的概念,因此 0 长度缓冲区是有效的,这就是 WSARecvFrom() 没有预先失败的原因)。对于 UDP,如果缓冲区太小,则丢弃数据报。没有办法恢复它,或者用更大的缓冲区重试读取相同的数据报。您必须在第一时间做对,否则就永远失去它。
  • 由于 UDP 每个数据报的最大缓冲区大小仅为 65535 字节,这是一个非常小的分配量,请继续为每个 UDP 读取分配最大缓冲区大小。完成会告诉你实际收到了多少字节。
  • 我挖了一下,你是对的,UDP数据将被丢弃并且无法恢复,真可惜。关于分配,如果我有 1000 个套接字等待数据,我需要分配 1000 个 64kb 缓冲区,所以这不是一个非常优雅的解决方案。我会接受你的回答,谢谢。
  • @Andre:每个读/写操作都需要一个单独的缓冲区。当一个操作完成后,如果需要,您可以将该缓冲区重新用于另一操作。但是,是的,如果您同时读取 1000 个套接字,则需要读取 1000 个单独的缓冲区。如果您不想分割系统内存,请考虑为缓冲区使用专用堆或内存池。否则,使用select()/WSA...Select() 来检测数据何时等待,并且仅根据需要分配+读取。
猜你喜欢
  • 1970-01-01
  • 2011-07-25
  • 2021-10-16
  • 1970-01-01
  • 1970-01-01
  • 2017-05-18
  • 2013-11-13
  • 1970-01-01
  • 2011-04-12
相关资源
最近更新 更多