【问题标题】:recv does not return after udp socket closed in linux在 linux 中关闭 udp 套接字后,recv 不返回
【发布时间】:2019-02-27 03:43:29
【问题描述】:

我正在尝试使用 udp 协议编写客户端服务器应用程序,但我遇到了连接结束问题。
我打开两个套接字(一个是“服务器”,另一个是“客户端”),当服务器从客户端异步接收时,客户端向他发送一条简单的消息,打印到控制台。
经过一段时间的睡眠(以确保服务器将再次调用 recv)客户端和服务器套接字关闭。 在这一点上,我预计 recv 将返回 -1 并且异步将结束。
但实际发生的是,recv 永远卡住了*。
如果就在关闭套接字之前我发送了一个空包(在代码中将 sendToMakeExit 变量设置为 true),recv 将返回该空包,并且仅在下一次调用后才返回 -1,尽管套接字在第一次调用中已关闭。

    const bool sendToMakeExit = false;
    const int port = 2000;
    const auto addr = "127.0.0.1";
    int serverSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    sockaddr_in target;
    target.sin_family = AF_INET;
    target.sin_port = htons(port);
    inet_pton(AF_INET, addr, &target.sin_addr);
    bind(serverSocket, (sockaddr *) &target, sizeof(target));
    auto readAsync = std::async(std::launch::async, [&serverSocket] {
        const int MAX_READ = 4096;
        char readBuf[MAX_READ];
        ssize_t actualRead;
        do {
            actualRead = recv(serverSocket, readBuf, MAX_READ, 0);
            if (actualRead > 0) {
                cout << readBuf << endl;
            }
        } while (actualRead != -1);
    });
    int clientSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    connect(clientSocket, (sockaddr *) &target, sizeof(target));
    this_thread::sleep_for(chrono::seconds(1));
    send(clientSocket, "test", 5, 0);
    this_thread::sleep_for(chrono::seconds(1));
    close(clientSocket);
    if (sendToMakeExit) {
        sendto(serverSocket, nullptr, 0, 0, (sockaddr *) &target, sizeof(target));
    }
    close(serverSocket);

*如果我在调试中运行此代码并在 recv 意外卡住时创建新断点,recv 返回 -1。
当我关闭套接字时,如何让 recv 返回 -1?

【问题讨论】:

  • 你的代码是C++,为什么要标记C?
  • 顺便说一句,当对等方已按顺序关闭另一个时,至少流套接字返回 0

标签: c++ linux sockets


【解决方案1】:

关闭一个套接字并不能保证另一个线程中仍在使用该套接字的任何函数调用立即返回。如果您有等待数据进入的调用,例如recv()select()poll(),则必须将一些数据发送到套接字以使这些调用返回。您在代码中执行此操作,但在收到长度为零的 UDP 数据包时实际上并没有退出:将 while 循环的结尾更改为:

} while (actualRead > 0);

但是,我建议使用一个标志变量来指示线程是否应该继续运行,如下所示:

volatile bool running = true;

auto readAsync = std::async(std::launch::async, [&serverSocket, &running] {
    ...
    while (running) {
        ...recv()...
    }
});

...
running = false;
sendto(serverSocket, ...);
readAsync.wait();
close(serverSocket);

注意我在关闭socket之前加了一行等待readAsync完成,为了防止意外发生:有一个小窗口,socket失效了,但是readAsync可能还是调用@ 987654330@就可以了。如果你有更多的线程,也可能会发生你关闭这个线程中的套接字,另一个线程打开一个新的套接字并获得与你刚刚关闭的相同的文件描述符编号,然后readAsync线程会使用错误插座。

【讨论】:

  • “如果您希望在客户端关闭 UDP 套接字时通知服务器,您必须让客户端发送一个指示该信息的数据包” - 比这稍微复杂一些。由于 UDP 是无连接的并且无法保证交付,因此到服务器的“关闭”数据包可能会丢失,并且服务器和客户端都不知道发生了这种情况。因此,您需要设计一个协议,让每一端都确认收到并在没有得到预期回复时重新发送(现在您已接近实现 TCP)。我建议查看ENet
  • 我知道 udp 是无连接的,并且我知道当我关闭客户端时没有数据发送到服务器。但在这里我也关闭了 server 套接字——recv 操作的那个。这就是为什么我希望在关闭recv的套接字后,函数将返回,因为套接字已关闭。
  • 所以我只需要在关闭套接字之前总是发送数据。知道为什么这种行为不会在 Windows 上发生吗?
  • 当套接字从另一个线程关闭时,也许 Windows 确实中止了 recv() 调用? recv()close() 函数的规范中没有提到多线程场景中的行为,因此由操作系统决定如何处理它。 Linux 通过让recv() 函数无限期地等待来处理它。另见:stackoverflow.com/a/6390101/5481471
猜你喜欢
  • 1970-01-01
  • 2011-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多