首页 > 解决方案 > 来自阻塞的意外 WSA_IO_PENDING(具有重叠的 I/O 属性)Winsock2 调用

问题描述

简短版本:使用阻塞套接字 API 调用时,我得到 WSA_IO_PENDING。我应该如何处理?套接字具有重叠的 I/O 属性并设置了超时。

长版

平台:Windows 10。Visual Studio 2015

套接字以非常传统的简单方式创建。

s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

默认情况下,套接字启用了重叠 I/O属性。可以使用getsockop / SO_OPENTYPE进行验证。

套接字启用超时和保持活动...

::setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, ...);

::setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, ...);

::WSAIoctl(s, SIO_KEEPALIVE_VALS, ...);

套接字操作通过

::send(s, sbuffer, ssize, 0);

::recv(s, rbuffer, rsize, 0);

我还尝试同时使用WSARecvWSASend并将其lpOverlapped设置lpCompletionRoutine为 NULL。

[MSDN] ... 如果 lpOverlapped 和 lpCompletionRoutine 都为 NULL,则此函数中的套接字将被视为非重叠套接字。

::WSARecv(s, &dataBuf, 1, &nBytesReceived, &flags, NULL/*lpOverlapped*/, NULL/*lpCompletionRoutine*/)

::WSASend(s, &dataBuf, 1, &nBytesSent, 0, NULL/*lpOverlapped*/, NULL/*lpCompletionRoutine*/)

问题

那些 send / recv / WSARecv / WSASend 阻塞调用将返回带有WSA_IO_PENDING错误代码的错误!

问题

Q0:有没有关于阻塞调用和超时的重叠属性的参考?

它的行为如何?如果我有一个启用了重叠“属性”+超时功能的套接字,并且只需使用具有“无重叠 I/O 语义”的阻塞套接字 API。

我找不到任何关于它的参考资料(例如来自 MSDN)。

Q1:这是预期的行为吗?

在将代码从 Win XP/Win 7 迁移到Win 10后,我观察到了这个问题(获取 WSA_IO_PENDING) 。

这是客户端代码部分:(注意:实际代码中没有使用断言,只是在这里描述了相应的错误将被处理,并且一个错误的套接字将停止该过程..)

    auto s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    assert(s != INVALID_SOCKET);

    timeval timeout;
    timeout.tv_sec = (long)(1500);
    timeout.tv_usec = 0;

    assert(::setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)) != SOCKET_ERROR);

    assert(::setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout)) != SOCKET_ERROR);

    struct tcp_keepalive
    {
      unsigned long onoff;
      unsigned long keepalivetime;
      unsigned long keepaliveinterval;
    } heartbeat;
    heartbeat.onoff             = (unsigned long)true;                         
    heartbeat.keepalivetime     = (unsigned long)3000;
    heartbeat.keepaliveinterval = (unsigned long)3000;
    DWORD nob = 0;

    assert(0 == ::WSAIoctl(s, SIO_KEEPALIVE_VALS, &heartbeat, sizeof(heartbeat), 0, 0, &nob, 0, 0));

    SOCKADDR_IN connection;
    connection.sin_family = AF_INET;
    connection.sin_port = ::htons(port);
    connection.sin_addr.s_addr = ip;

    assert(::connect(s, (SOCKADDR*)&connection, sizeof(connection)) != SOCKET_ERROR);

    char buffer[100];
    int receivedBytes = ::recv(s, buffer, 100, 0);

    if (receivedBytes > 0)
    {
      // process buffer
    }
    else if (receivedBytes == 0)
    {
      // peer shutdown
      // we will close socket s
    }
    else if (receivedBytes == SOCKET_ERROR)
    {
      const int lastError = ::WSAGetLastError();
      switch (lastError)
      {
      case WSA_IO_PENDING:
          //.... I get the error!
      default:
      }
    }

Q2:我应该如何处理?

忽略它?或者只是关闭套接字作为通常的错误情况?

从观察来看,一旦我得到 WSA_IO_PENDING,如果我忽略它,套接字最终将变得不再响应......

Q3:WSAGetOverlappedResult怎么样?

这有什么意义吗?

我应该给什么 WSAOVERLAPPED 对象?由于没有这样一个我用于所有那些阻塞套接字调用。

我试过创建一个新的空 WSAOVERLAPPED 并用它来调用 WSAGetOverlappedResult。它最终会成功返回,传输 0 字节。

标签: socketswindows-10winsock2overlapped-io

解决方案


Q3:怎么样WSAGetOverlappedResult

[WSA]GetOverlappedResult我们只能使用指针WSAOVERLAPPED传递给I/O请求。使用任何其他指针都是没有意义的。有关I/O操作的所有信息WSAGetOverlappedResult都来自lpOverlapped(最终状态、传输的字节数、如果需要等待 - 它等待来自此重叠的事件)。笼统地说 - 每个I/O请求都必须将OVERLAPPEDIO_STATUS_BLOCK真正的)指针传递给内核。内核直接修改内存(最终状态和信息(通常是传输的字节)。因为此生命周期OVERLAPPED必须在I/O未完成之前有效。并且对于每个I/O请求必须是唯一的。[WSA]GetOverlappedResult检查此内存OVERLAPPEDIO_STATUS_BLOCK真的) - 首先寻找状态。如果它是另一个来自STATUS_PENDING- 这意味着操作已完成 - api 获取传输的字节数并返回。如果还在STATUS_PENDING这里 -I/O尚未完成。hEvent如果我们想要等待 - 使用重叠的API来等待。此事件句柄在I/O请求期间传递给内核,并在I/O完成时设置为信号状态。等待任何其他事件是没有意义的——它与具体的I/O请求有什么关系?现在想想必须清楚为什么我们[WSA]GetOverlappedResult只能通过传递给I/O请求的完全重叠的指针来调用。

如果我们不将指针传递给您OVERLAPPED自己(例如,如果我们使用recvor send)低级套接字 api - 您自己OVERLAPPED在堆栈中分配为局部变量并将其指针传递给I/O结果 - 在这种情况下,直到I/O未完成,api 才能返回。因为重叠的内存必须有效,直到I/O未完成(完成内核将数据写入此内存)。但是我们离开函数后局部变量变得无效。所以函数必须等待到位。

因为这一切我们不能[WSA]GetOverlappedResultsendor之后调用recv- 起初我们根本没有指向重叠的指针。在I/O请求中使用的第二次重叠已经“销毁”(更准确地说是在顶部下方的堆栈中 - 所以在垃圾区中)。如果I/O尚未完成 - 内核已经在随机位置堆栈中修改数据,当它最终完成时 - 这将产生不可预知的影响 - 从没有任何事情发生 - 崩溃或非常不寻常的副作用。如果send或在I/Orecv完成之前返回- 这将对进程产生致命影响。这绝不是(如果 Windows 中没有错误)。

Q2:我应该如何处理?

我如何尝试解释是否WSA_IO_PENDING真的由sendor返回recv- 这是系统错误。如果设备以这样的结果完成I/O则很好(尽管不能) - 只是一些未知的(对于这种情况)错误代码。像处理任何一般错误一样处理它。不需要特殊处理(例如异步 io)。如果I/O确实尚未完成(之后sendrecv返回) - 这意味着在随机时间(可能已经)您的堆栈可能会损坏。这种不可预知的影响。在这里什么也做不了。这是严重的系统错误。

Q1:这是预期的行为吗?

不,这绝对不例外。

Q0:有没有关于阻塞调用和超时的重叠属性的参考?

首先,当我们创建文件句柄时,我们设置或不设置异步属性:如果是CreateFileW- FILE_FLAG_OVERLAPPED,如果是WSASocket- WSA_FLAG_OVERLAPPED。万一NtOpenFileNtCreateFile- FILE_SYNCHRONOUS_IO_[NO]NALERT(反向效果比较FILE_FLAG_OVERLAPPED)。所有这些信息存储在- (文件对象为同步 I/O 打开。)将被设置或清除。FILE_OBJECT.FlagsFO_SYNCHRONOUS_IO

FO_SYNCHRONOUS_IO标志的作用是下一个: I/O子系统通过调用某些驱动程序IofCallDriver,如果驱动程序返回STATUS_PENDING- 如果FO_SYNCHRONOUS_IO设置了标志FILE_OBJECT- 就地等待(所以在内核中),直到I/O未完成。否则返回此状态 -STATUS_PENDING对于调用者 - 它可以等待自己到位,或通过APCIOCP接收者回调。

当我们使用socket它内部调用时WSASocket-

创建的套接字默认具有重叠属性

这意味着文件将没有FO_SYNCHRONOUS_IO属性,并且低级 I/O调用可以STATUS_PENDING从内核返回。但让我们看看recv是如何工作的:

内部WSPRecv调用lpOverlapped = 0. 因为这 -WSPRecv你自己在堆栈中分配OVERLAPPED,作为局部变量。在通过. _ 因为没有标志ZwDeviceIoControlFile创建的文件(套接字) -从内核返回。在这种情况下,看看 - 是。如果是 - 它不能返回,直到操作未完成。它通过-开始等待事件(内部维护此套接字的用户模式)。如果您未设置,则使用与套接字关联的值或 0(无限等待)。如果 返回(这只能在您通过 设置超时的情况下) - 这意味着I/OFO_SYNCHRONOUSSTATUS_PENDINGWSPRecvlpOverlapped == 0SockWaitForSingleObjectZwWaitForSingleObjectTimeoutSO_RCVTIMEOSO_RCVTIMEOZwWaitForSingleObjectSTATUS_TIMEOUTSO_RCVTIMEO操作未在例外时间内完成。在这种情况下WSPRecv称为SockCancelIo(与 相同的效果CancelIo)。在文件上的所有I/O请求(来自当前线程)完成CancelIo之前,不得返回(等待) 。在此之后从重叠中读取最终状态。这里必须是(但实际上具体驱动程序决定哪个状态完成取消)。转换为. _ 然后调用将 ntstatus 代码转换为 win32 错误。说转换为. 但如果仍然重叠,那么 - 你得到了。仅在这种情况下。看起来像设备错误,但我无法在自己的 win 10 上重现它(可能是版本发挥作用)WSPRecvSTATUS_CANCELLEDIRPWSPRecvSTATUS_CANCELLEDSTATUS_IO_TIMEOUTNtStatusToSocketErrorSTATUS_IO_TIMEOUTWSAETIMEDOUTSTATUS_PENDINGCancelIoWSA_IO_PENDING


在这里可以做什么(如果你确定真的得到了WSA_IO_PENDING)?首先尝试使用WSASocketwithout WSA_FLAG_OVERLAPPED- 在这种情况下ZwDeviceIoControlFile永远不会返回STATUS_PENDING,你永远不会得到WSA_IO_PENDING. 检查这个 - 错误消失了吗?如果是 -返回重叠属性并删除SO_RCVTIMEO调用(所有这些都是为了测试- 不是发布产品的解决方案)并在此错误消失后进行检查。如果是 - 看起来像设备无效取消(带有STATUS_PENDING?!?)IRP. 这一切的意义——找出错误更具体的地方。无论如何,有趣的是构建最小的演示 exe,它可以稳定地重现这种情况并在另一个系统上测试它 - 这会持续吗?仅适用于具体版本?如果它不能在另一个组合上重现 - 需要在你的混凝土上进行调试


推荐阅读