为啥winsock 不在这里提供WSAESHUTDOWN?

Posted

技术标签:

【中文标题】为啥winsock 不在这里提供WSAESHUTDOWN?【英文标题】:Why isn't winsock delivering WSAESHUTDOWN here?为什么winsock 不在这里提供WSAESHUTDOWN? 【发布时间】:2021-11-09 04:20:32 【问题描述】:

最小的、可重现的例子:

#include <cassert>
#include <thread>
#include <WinSock2.h>
#pragma comment(lib, "Ws2_32.lib")


constexpr int BUF_SZ = 512;
void RecvThread(SOCKET sock)

    int iResult = 0;
    char buf[BUF_SZ] = ;

    iResult = recv(sock, buf, BUF_SZ, 0);

    assert(iResult == SOCKET_ERROR);
    assert(WSAGetLastError() == WSAESHUTDOWN);



int main()

    int iResult;

    WSADATA wsaData;
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    assert(iResult == 0);

    SOCKET sock_1 = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    assert(sock_1 != INVALID_SOCKET);

    SOCKET sock_2 = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    assert(sock_2 != INVALID_SOCKET);

    in_addr loopback = ;
    loopback.S_un.S_un_b.s_b1 = 127;
    loopback.S_un.S_un_b.s_b2 = 0;
    loopback.S_un.S_un_b.s_b3 = 0;
    loopback.S_un.S_un_b.s_b4 = 1;

    sockaddr_in addr_1 = ;
    addr_1.sin_family = AF_INET;
    addr_1.sin_port = 51234;
    addr_1.sin_addr = loopback;

    iResult = bind(sock_1, (sockaddr const*)&addr_1, sizeof addr_1);
    assert(iResult != SOCKET_ERROR);

    sockaddr_in addr_2 = ;
    addr_2.sin_family = AF_INET;
    addr_2.sin_port = 51235;
    addr_2.sin_addr = loopback;

    iResult = bind(sock_2, (sockaddr const*)&addr_2, sizeof addr_2);
    assert(iResult != SOCKET_ERROR);

    std::thread t1(RecvThread, sock_1);
    std::thread t2(RecvThread, sock_2);

    iResult = shutdown(sock_1, SD_BOTH);
    assert(iResult != SOCKET_ERROR);
    t1.join(); // after shutdown, join the recv thread

    iResult = shutdown(sock_2, SD_BOTH);
    assert(iResult != SOCKET_ERROR);
    t2.join(); // after shutdown, join the recv thread

    // note: everything works if we delay t1.join() until after shutdown(sock_2)

    iResult = closesocket(sock_1);
    assert(iResult != SOCKET_ERROR);

    iResult = closesocket(sock_2);
    assert(iResult != SOCKET_ERROR);

    iResult = WSACleanup();
    assert(iResult != SOCKET_ERROR);

    return 0;

这在尝试加入在recv 上被阻止的recv 线程时挂起。如果所有创建的套接字都已shutdown,则传递WSAESHUTDOWN,程序干净退出。

我是不是做错了什么,误解了 shutdown 应该如何工作,或者这是一个 Windows 错误?

由于这可能是一个 Windows 错误,我使用的是 Microsoft Windows 版本 21H1(操作系统内部版本 19043.1165)。

我希望在我shutdown 套接字时收到WSAESHUTDOWN,当我shutdown 两个套接字时会发生这种情况,但是如果我shutdown 一个套接字然后阻塞线程连接,程序就会挂起。我正在尝试诊断的正是这个问题。

更新:最初的问题使用了recv,但显然这是 UDP 套接字的问题,recvrecvfrom 都会出现缺少关闭错误。

【问题讨论】:

shutdown() 在 UDP 套接字上不会通过线路发送任何内容,因此它不会影响对等方的 recv()recvfrom() 或他正在阅读的任何内容。它所做的只是阻止您在套接字上再次发送。它不是 TCP,它不提供 FIN。 @user207421 shutdown() 可能不会向对等方发送任何内容(此外,此代码中无论如何都没有分配对等方,因为没有调用connect()),但它仍然会影响正在调用recv/from() 的套接字。此代码在两个套接字上调用 shutdown() 我不确定是否更正在问题中使用recv 的错误(而不是recvfrom)会改变太多,但我选择更正问题而不是提出新问题几乎重复。使用正确的recv 操作,为什么这里没有提出WAESHUTDOWN 的原始问题。事实上,在调试器中我总是看到第一个套接字被关闭,但不是第二个。 【参考方案1】:

您不能在 UDP 套接字上使用 recv(),除非您首先调用 connect() 将远程对等方与套接字静态关联。否则,recv() 无法返回任何数据报,因为它不知道哪些数据报来自所需的对等方,因此它将丢弃所有数据。

由于您没有调用connect(),因此您应该在两个线程中都收到WSAENOTCONN 错误。

如果您不打算使用connect() 套接字,则需要改用recvfrom()

【讨论】:

啊,我希望我没有“简化”为recvWSAESHUTDOWN 的缺失与 recvfrom 相同。 @CAD97 在这种情况下,代码对我来说看起来不错,并且行为应该按预期运行,所以它一定是 Windows 错误。也就是说,一个简单的解决方案是向套接字的端口发送一个数据包以解除对recvfrom() 的阻塞,或者将WSARecvFrom()WSAWaitForMultipleEvents() 一起使用,这样您就可以等待可以在需要时发出信号的辅助事件。 如果您将该花絮添加到您的答案中,我会很乐意将其标记为解决方案。 (评论是暂时的等)

以上是关于为啥winsock 不在这里提供WSAESHUTDOWN?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 printf 不在这里打印?

为啥 g++ 不在这里执行结构打包?

为啥 Oracle 不在这里抛出“不明确的列引用”?

为啥我的 Winsock 应用程序有时在 listen() 处等待,有时在 accept() 处等待?

为啥 GCM 不在 android 设备中提供推送通知?

为啥 CertificateRequest 的 PublicKey.Key 与私钥不在同一个提供者中?