意外WSA_IO_PENDING阻止了Winsock2调用(具有重叠的I / O属性)

时间:2018-09-20 07:39:37

标签: sockets windows-10 winsock2 overlapped-io

简短版本 : 使用阻塞套接字API调用时出现WSA_IO_PENDING。我应该如何处理?套接字具有overlapped I/O attribute并设置了超时。

长版

平台:Windows10。Visual Studio 2015

socket是用非常传统的简单方法创建的。

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

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

  • 我确实需要重叠属性,因为我想使用超时功能,例如SO_SNDTIMEO
  • 我只会以阻塞(即同步)的方式使用套接字。
  • 套接字读取操作仅在单个线程中运行。
  • 套接字写操作可以从与互斥锁同步的不同线程执行。

套接字已启用超时功能,并通过...保持活动状态

::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);

我还尝试将lpOverlappedlpCompletionRoutine都设置为NULL的WSARecvWSASend

  

[MSDN] ...如果lpOverlapped和lpCompletionRoutine均为NULL,则套接字在   此功能将被视为不重叠的套接字。

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

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

问题

那些发送/接收/ WSARecv / WSASend阻止调用将返回错误代码为 WSA_IO_PENDING 的错误!

问题

Q0:关于重叠属性的任何引用都具有阻塞的调用和超时?

它的表现如何? 如果我的套接字具有重叠的“属性” +超时功能启用,并且仅使用具有“无重叠I / O语义”的阻塞套接字API。

我找不到有关它的任何参考(例如,来自MSDN)。

第一季度:这是预期的行为吗?

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

这是客户端代码部分:(请注意:assert不在实际代码中使用,而仅在此处描述将处理相应的错误,并且有错误的套接字将停止该过程。)

    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:
      }
    }

第二季度:我应该如何处理?

忽略它吗?还是只是在通常的错误情况下关闭套接字?

从观察结果中,一旦我得到WSA_IO_PENDING,并且如果我只是忽略它,套接字最终将变得不再响应。

第三季度:WSAGetOverlappedResult怎么样?

这有意义吗?

我应该给哪个WSAOVERLAPPED对象?由于没有这样的程序,因此我将所有阻塞套接字调用都使用。

我尝试仅创建一个新的空WSAOVERLAPPED并使用它来调用WSAGetOverlappedResult。最终将成功返回0字节并返回。

1 个答案:

答案 0 :(得分:1)

  

Q3:WSAGetOverlappedResult怎么样?

[WSA]GetOverlappedResult中,我们只能使用指向传递给 I / O 请求的WSAOVERLAPPED的指针。使用任何其他指针是没有意义的。有关 I / O 操作WSAGetOverlappedResult的所有信息均来自lpOverlapped(最终状态,已传输的字节数,如果需要等待-它等待与此重叠的事件)。一般而言-每个 I / O 请求都必须将OVERLAPPED(实际上是IO_STATUS_BLOCK)指针传递给内核。内核直接修改内存(最终状态和信息(通常是字节传输)),因为OVERLAPPED的生存期必须有效,直到 I / O 不完整为止,并且对于每个必须是唯一的I / O 请求。[WSA]GetOverlappedResult检查此存储器OVERLAPPED(实际上是IO_STATUS_BLOCK)-首先查找状态,如果它来自STATUS_PENDING,则为-表示操作已完成-api接收传输并返回的字节数。如果此处仍为STATUS_PENDING-I/O尚未完成。如果我们要等待-api使用hEvent进行重叠等待。事件句柄在 I / O 请求期间传递给内核,并在 I / O 完成时将其设置为信号状态。具体的 I / O 请求?现在想清楚为什么我们只有在传递给 I / O 完全重叠指针的情况下才能调用[WSA]GetOverlappedResult >请求。

如果我们自己没有传递指向OVERLAPPED的指针(例如,如果我们使用recvsend),则底层套接字api-您自己将OVERLAPPED分配为堆栈并将其指针传递给 I / O 。结果-在 I / O 未完成之前,api在这种情况下无法返回。因为重叠的内存必须有效,直到 I / O 未完成(完成时内核将数据写入该内存)。但是离开函数后局部变量变得无效。因此功能必须等待到位。

因为所有这些,我们无法在[WSA]GetOverlappedResultsend之后调用recv-首先,我们根本没有指向重叠的指针。在第二次重叠中,在 I / O 请求中使用的请求已被“销毁”(更确切地说,位于顶部下方的堆栈中-因此位于垃圾区域)。如果 I / O 尚未完成-内核已经在随机位置堆栈中修改了数据,那么当它最终完成时-这将具有不可预测的效果-不会发生任何事情-崩溃或非常不正常的副作用。如果sendrecv I / O 完成之前返回-这将对过程产生致命影响。这绝对不是必须的(如果Windows中没有错误)。

  

Q2:我应该如何处理?

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

  

问题1:这是预期的行为吗?

不,这绝对不例外。

  

Q0:重叠属性上的任何引用都具有阻塞调用和   超时?

首先,当我们创建文件句柄时,我们在其上设置或不设置异步属性:在CreateFileW-FILE_FLAG_OVERLAPPED的情况下,在WSASocket-WSA_FLAG_OVERLAPPED的情况下。如果是NtOpenFileNtCreateFile-FILE_SYNCHRONOUS_IO_[NO]NALERT(反向比较FILE_FLAG_OVERLAPPED)。所有存储在FILE_OBJECT .Flags-FO_SYNCHRONOUS_IO已为同步I / O打开文件对象。)中的信息都将被设置或清除。

接下来是FO_SYNCHRONOUS_IO标志的

效果: I / O 子系统通过IofCallDriver调用某个驱动程序,并且如果驱动程序返回STATUS_PENDING-在{{1}的情况下} FO_SYNCHRONOUS_IO中设置的标志-等待就位(因此在内核中),直到 I / O 未完成。否则返回此状态-FILE_OBJECT供呼叫者使用-它可以等待您就位,或者通过 APC IOCP 进行接收者回调。

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

  

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

此平均文件将不具有WSASocket属性,并且低级别 I / O 调用可以从内核返回FO_SYNCHRONOUS_IO。但让我们看看recv的工作方式:

内部WSPRecvSTATUS_PENDING调用。因为这-lpOverlapped = 0自己在栈中分配WSPRecv作为局部变量。之前通过OVERLAPPED发出实际的 I / O 请求。因为创建的文件(套接字)没有ZwDeviceIoControlFile标志-FO_SYNCHRONOUS是从内核返回的。在这种情况下,STATUS_PENDING的外观-为WSPRecv。如果是,则无法返回,直到操作完成。它开始通过lpOverlapped == 0-ZwWaitForSingleObject等待事件(此套接字在用户模式下内部维护)。在适当的位置SockWaitForSingleObject使用您通过Timeout与套接字关联的值,如果未设置SO_RCVTIMEO,则使用0(无限等待)。如果SO_RCVTIMEO返回ZwWaitForSingleObject(仅在通过STATUS_TIMEOUT设置超时的情况下)-这意味着 I / O 操作不会在指定时间内完成。在这种情况下,SO_RCVTIMEO称为WSPRecv(与CancelIo相同)。 CancelIo不得返回(等待)文件(来自当前线程)的所有 I / O 请求都将完成。在此SockCancelIo之后,从重叠状态读取最终状态。这里必须是WSPRecv(但实际上,具体的驱动程序决定以哪个状态完成取消的STATUS_CANCELLED)。 IRPWSPRecv转换为STATUS_CANCELLED。然后调用STATUS_IO_TIMEOUT将ntstatus代码转换为win32错误。将NtStatusToSocketError转换为STATUS_IO_TIMEOUT。但是如果WSAETIMEDOUT仍然重叠,则在STATUS_PENDING之后-您得到了CancelIo。仅在这种情况下。看起来像设备错误,但我无法在自己的Win 10上重制它(可能是版本扮演角色)


在这里可以做什么(如果您确定确实有WSA_IO_PENDING)?首先尝试使用不带WSA_IO_PENDING的{​​{1}}-在这种情况下,WSASocket永远不会返回WSA_FLAG_OVERLAPPED,并且您永远都不会得到ZwDeviceIoControlFile。检查一下-错误消失了吗?如果是,-返回重叠的属性,然后删除STATUS_PENDING调用(所有测试-不是发行产品的解决方案),并检查此错误是否消失。如果是,则-设备无效取消(使用WSA_IO_PENDING?!?) IRP 。所有这些的意义-找到错误更具体的地方。无论如何,有趣的是将构建最小的演示exe,它可以稳定地重现这种情况并在另一个系统上对其进行测试-这会持续吗?仅适用于具体版本吗?如果无法在其他伴奏上重现-需要在您的混凝土上调试