尝试退出阻塞的UDP套接字读取

3
这是一个类似于 Proper way to close a blocking UDP socket的问题。我有一个在C语言中读取UDP套接字的线程,读取是阻塞的。我想知道是否可能在不依赖于recv()返回的情况下退出线程?例如,我可以从另一个线程关闭套接字并安全地期望套接字读取线程退出吗?在那个线程中没有看到任何高票答案,这就是为什么我再次提问的原因。
5个回答

4
这实际上取决于您正在运行的系统。例如,如果您正在运行POSIX兼容系统并且您的线程是可取消的,则在取消线程时recv()调用将被中断,因为它是一个取消点。
如果您使用较旧的套接字实现,则可以为您的线程设置信号处理程序,例如SIGUSR1,并希望没有其他人想要它并发出信号,因为recv()将在信号上中断。如果可能的话,最好不要阻塞。

取消似乎是一个很有前途的想法。有没有关于线程取消点的文档可用?我看到了这个链接(http://www.mkssoftware.com/docs/man3/pthread_cancel.3.asp),它没有列出任何与套接字相关的调用。或者像`recv()`一样,它会使用其他调用,例如`read()`? - bobby
如果您的系统符合POSIX标准并使用POSIX标准套接字,那么它将是兼容的。POSIX取消点。 - Jason Coco

3
我认为关闭一个参与阻塞操作的套接字并不是一种安全可靠的终止该操作的方式。例如,kernel.org暗示:
“在同一进程中的其他线程中使用系统调用时,关闭文件描述符可能不明智。由于文件描述符可能被重用,存在一些难以理解的竞争条件可能会导致意外的副作用。”
您可以采用以下方法代替关闭套接字:
  • 您可以使用信号,并使recv失败并返回EINTR(确保未启用SA_RESTART)。您可以使用pthread_kill向特定线程发送信号。

  • 在开始recv调用之前,您可以在套接字上启用SO_RCVTIMEO

个人而言,我通常尝试避免使用所有的信号机制,但这是一种可行的选项。

2

针对这个问题,你有几个选项。信号将中断读取操作,所以你需要确保发出信号。recv操作应该会失败,并返回错误号EINTR。

最简单的方法是设置一个定时器,在一定时间后(例如30秒)中断进程:

itimerval timer
timeval time;
time.tv_sec = 30;
time.tv_usec = 0;
timer.it_value = time;
if( setitimer( ITIMER_REAL, &timer, NULL ) != 0 )
  printf( "failed to start timer\n" );

在指定的时间之后,您将收到一个SIGALRM信号,这将中断您的阻塞操作,并为您提供重复操作或退出的机会。


2

当另一个线程正在使用或可能使用共享资源时,您无法释放该资源。实际上,你会发现你甚至无法编写代码来做你所建议的事情。

想一想。当你调用 close 时,你怎么可能知道另一个线程实际上被阻塞在 recv 中?如果它即将调用 recv,但是另一个线程调用了 socket 并获取了你刚刚关闭的描述符,那么不仅该线程不会检测到任何错误,而且它将在 错误的 套接字上调用 recv

解决你需要退出阻塞 UDP 套接字读取的外部问题可能有一种好方法。还有几种丑陋的 hack 方法可用。基本方法是使套接字非阻塞,而不是进行阻塞 UDP 套接字读取,使用 selectpoll 来模拟阻塞读取。然后可以通过以下几种方式中止此循环:

一种方法是让 select 超时并在 select 返回时检查“中止”标志。

另一种方法是在管道的读端上也进行 select。发送一个字节到管道来中止 select


1
如果是符合posix标准的系统,您可以尝试监视线程:pthread_create并使用一个函数来执行您的recv和在此之后执行pthread_cond_signal,然后返回。
调用线程使用所需超时时间进行pthread_cond_timedwait,并在超时时终止被调用线程。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接