如何从另一个线程可移植可靠地取消阻止/中断 UDP 套接字上的"recv"?



>问题

有一个线程在循环中的阻塞 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虽然本身失败了,但完成了这项工作:
[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在那里工作...

仅给定 UDP 套接字本身,唤醒线程的唯一真正可移植的选项是:

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

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

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

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

相关内容

  • 没有找到相关文章

最新更新