如何在另一个线程的 UDP 套接字上便携可靠地 unblock/interrupt `recv`?
How do I portably and reliably unblock/interrupt `recv` on a UDP socket from another thread?
问题
循环中阻塞的 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;
}
描述:
- 它创建 UDP 套接字
- 它启动另一个线程,从该套接字同步接收数据包。
- 主线程决定停止。
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
对 non-connected/UDP 套接字的影响? 没有。
- 是否可以对套接字执行其他操作以可靠且可移植地中断相邻线程上的
recv
?
更新:似乎在 Mac 上失败了。 close
然而在那里确实有效...
仅考虑到 UDP 套接字本身,唯一真正可移植的唤醒线程的选项是:
将套接字切换到非阻塞模式,然后让线程使用select()
或(e)poll()
来检测何时可以从套接字读取入站数据包。这样,您可以在每次等待时指定超时,并且线程可以在等待之间检查是否终止。
使用sendto()
向socket的监听端口发送一个私有的UDP包来唤醒一个阻塞的recv()
/recvfrom()
调用,那么线程就会免费检查终止。请注意,如果您使用 connect()
将 UDP 套接字与对等点 IP/port 静态关联,这将不起作用,因为这样做会限制 recv()
/recvfrom()
从接收来自其他来源。在您提供的示例中情况并非如此,但您应该注意这一点。
在许多平台上,您可以通过 setsockopt(SO_RCVTIMEO)
使用阻塞读取超时,然后您可以为每次读取指定一个超时,并且线程可以在读取之间检查是否终止。但并非所有平台都支持 SO_RCVTIMEO
.
否则,您可以打开第二个 UDP 套接字或管道,并让线程使用 select()
或 (e)poll()
与主 UDP 套接字同时监视它。不要在主套接字上调用 recv()
/recvfrom()
除非确实有东西可以从中读取。 Connect/send 到第二个 pipe/socket 当你想唤醒线程时。
问题
循环中阻塞的 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;
}
描述:
- 它创建 UDP 套接字
- 它启动另一个线程,从该套接字同步接收数据包。
- 主线程决定停止。
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
对 non-connected/UDP 套接字的影响?- 是否可以对套接字执行其他操作以可靠且可移植地中断相邻线程上的
recv
?
更新:似乎在 Mac 上失败了。 close
然而在那里确实有效...
仅考虑到 UDP 套接字本身,唯一真正可移植的唤醒线程的选项是:
将套接字切换到非阻塞模式,然后让线程使用
select()
或(e)poll()
来检测何时可以从套接字读取入站数据包。这样,您可以在每次等待时指定超时,并且线程可以在等待之间检查是否终止。使用
sendto()
向socket的监听端口发送一个私有的UDP包来唤醒一个阻塞的recv()
/recvfrom()
调用,那么线程就会免费检查终止。请注意,如果您使用connect()
将 UDP 套接字与对等点 IP/port 静态关联,这将不起作用,因为这样做会限制recv()
/recvfrom()
从接收来自其他来源。在您提供的示例中情况并非如此,但您应该注意这一点。
在许多平台上,您可以通过 setsockopt(SO_RCVTIMEO)
使用阻塞读取超时,然后您可以为每次读取指定一个超时,并且线程可以在读取之间检查是否终止。但并非所有平台都支持 SO_RCVTIMEO
.
否则,您可以打开第二个 UDP 套接字或管道,并让线程使用 select()
或 (e)poll()
与主 UDP 套接字同时监视它。不要在主套接字上调用 recv()
/recvfrom()
除非确实有东西可以从中读取。 Connect/send 到第二个 pipe/socket 当你想唤醒线程时。