如何在另一个线程的UDP套接字上可移植且可靠地解除阻塞/中断`recv`?

时间:2017-08-02 22:22:30

标签: multithreading sockets udp portability

问题

在循环中的阻塞UDP套接字上有一个运行recv的线程。我们需要停止该线程并关闭该套接字。

示例:

#include <unistd.h>
#include <sys/socket.h>

#include <thread>
#include <atomic>

std::atomic<int> the_socket;

void threadf() {
    for(;;) {
        char c;
        int ret = recv(the_socket, &c, 1, 0);
        if(ret == 0 || ret == -1) {
            break;
        }
    }
    close(the_socket);
}

int main() {
    the_socket = socket(AF_INET, SOCK_DGRAM, 0);
    std::thread *t = new std::thread(threadf);
    usleep(200000);
    shutdown(the_socket, SHUT_RDWR); // ENOTCONN
    t->join();
    delete t;
    return 0;
}

说明

  1. 创建UDP套接字
  2. 它启动另一个同步接收来自该套接字的数据包的线程。
  3. 主线程决定是时候停止了。 delete t是一个坏主意。 close(the_socket)不会解除对相邻线程的阻止。但是shutdown虽然本身失败了,但却完成了这项工作:
  4. [pid 22289] nanosleep({tv_sec=0, tv_nsec=200000000},  <unfinished ...>
    [pid 22290] <... set_robust_list resumed> ) = 0
    [pid 22290] recvfrom(3,  <unfinished ...>
    [pid 22289] <... nanosleep resumed> NULL) = 0
    [pid 22289] shutdown(3, SHUT_RDWR)      = -1 ENOTCONN (Transport endpoint is not connected)
    [pid 22290] <... recvfrom resumed> "", 1, 0, NULL, NULL) = 0
    

    问题

    • 这种方案的便携性和可靠性如何?不,根本不可携带。
    • 多线程环境中shutdown的确切行为是否记录在某处?否。
    • shutdown对非连接/ UDP套接字的影响是否记录在某处?否。
    • 可以对套接字做些其他事情,以便可靠且可移植地中断相邻线程上的recv吗?

    更新:似乎在Mac上失败了。 close然而在那里工作......

1 个答案:

答案 0 :(得分:2)

仅提供UDP套接字,唤醒线程的唯一真正可移植选项是:

  1. 将套接字切换到非阻塞模式,然后让线程使用select()(e)poll()来检测何时可以从套接字读取入站数据包。这样,您可以在每次等待时指定超时,并且线程可以检查等待之间的终止。

  2. 使用sendto()将私有UDP数据包发送到套接字的侦听端口以唤醒阻塞recv() / recvfrom()调用,然后该线程可以自由检查终止。请注意,如果您使用connect()将UDP套接字与对等IP /端口静态关联,则此操作无效,因为这样做会限制recv() / recvfrom()接收来自其他来源的任何数据包。在您提供的示例中并非如此,但您应该注意这一点。

  3. 在许多平台上,您可以通过setsockopt(SO_RCVTIMEO)使用阻塞读取超时,然后您可以在每次读取时指定超时,并且线程可以检查读取之间的终止。但并非所有平台都支持SO_RCVTIMEO

    否则,您可以打开第二个UDP套接字或管道,并让线程使用select()(e)poll()同时监视主UDP套接字。不要在主套接字上调用recv() / recvfrom(),除非实际上有东西可以从中读取。当你想要唤醒线程时,连接/发送到第二个管道/插座。