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

标签 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
  • 而且我只会以阻塞(即同步)的方式使用套接字。
  • 套接字读取操作仅在单个线程中运行。
  • 套接字写操作可以从与互斥锁同步的不同线程执行。

  • 套接字已启用超时,并通过以下命令保持 Activity 状态:
    ::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] ... If both lpOverlapped and lpCompletionRoutine are NULL, the socket in this function will be treated as a non-overlapped socket.


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

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

    将代码从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:
          }
        }
    

    Q2:我应该如何处理?

    忽略它?还是只是关闭套接字作为常见的错误情况?

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

    问题3:WSAGetOverlappedResult怎么样?

    有什么意义吗?

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

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

    最佳答案

    Q3: How about 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尚未完成-内核已经在随机位置堆栈中修改了数据,则当它最终完成时-这将具有不可预测的效果-从无任何 react -崩溃或非常不正常的副作用。如果sendrecv在I/O完成之前返回-这将对进程产生致命影响。这绝对不是必须的(如果Windows中没有错误)。

    Q2: How should I handle it?



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

    Q1: is it expected behavior?



    不,绝对没有异常(exception)。

    Q0: any reference on overlapped attribute with blocking call and timeout?



    首先,当我们创建文件句柄时,我们在其上设置或不设置异步属性:在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,以防在FO_SYNCHRONOUS_IO中设置了FILE_OBJECT标志的情况下,就位(因此在内核中),直到I/O未完成。否则,返回此状态-调用者的STATUS_PENDING-它可以等待您就位,或者通过APC或IOCP进行接收者回调。

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

    The socket that is created will have the overlapped attribute as a default



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

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

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

    关于sockets - 意外的WSA_IO_PENDING阻止了Winsock2调用(具有重叠的I/O属性),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52419993/

    相关文章:

    node.js - 尝试运行 socket.io 示例时出现跨源请求被阻止错误

    c# - 如何使用 C# 代码实现 100% cpu 负载

    c# - UWP 并发线程

    python - Pyinstaller编译的exe找不到绝对路径的文件

    c - 网络接口(interface)名称列表,使用 C 和 Winsock?

    c++ - 获取 WSA 错误代码的格式化消息

    swift - TCP 套接字流和 SSL 与 Swift

    python - python套接字编程: error trying to connect to server

    c++ - 提升 ASIO : SSL handshake() never finishes

    c++ - 如何使 Ws2_32.lib 与针对 Windows 8.1 的 VS 2013 项目一起使用