在Linux中关闭udp套接字后recv不返回

时间:2018-09-23 11:21:08

标签: c++ linux sockets

我正在尝试使用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);

*如果我在debug中运行此代码并在recv卡住时创建新的断点,则recv会以-1返回。
关闭插槽时,如何使recv返回-1?

1 个答案:

答案 0 :(得分:2)

关闭套接字并不能保证仍在使用该套接字的另一个线程中的任何函数调用都会立即返回。如果您有等待数据进入的调用,例如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);

请注意,为了防止发生任何意外,我添加了一行等待readAsync在关闭套接字之前完成:有一个小窗口,套接字无效,但是readAsync可能仍会调用recv()。如果您有更多线程,则可能还会关闭该线程中的套接字,另一个线程将打开一个新套接字,并获得与刚刚关闭的套接字相同的文件描述符号,然后readAsync线程将使用错误的套接字。