当在一个套接字上进行 send/recv 操作时,另一个线程是否可以关闭该套接字?

15

在进行send/recv操作的同时,另一个线程能否关闭同一socket?

假设一个线程正在阻塞地执行recv调用,而另一个线程关闭了相同的socket,那么正在执行recv调用的线程会知道这个情况并安全地退出吗?

我想知道,不同的操作系统/平台之间是否存在行为差异。如果有,Solaris将如何表现?

5个回答

11
在Linux中关闭套接字不会唤醒recv()。 此外,正如@jxh所说:
 

如果一个线程在收到数据时被阻塞,但在此期间由不同的线程关闭了套接字,则被阻塞的线程将收到一个错误。 然而,很难在接收到错误之后检测到正确的补救措施。 这是因为与套接字相关联的文件描述符号可能已经被另一个线程拾取,并且阻塞的线程现在已经在对“有效”套接字的错误上被唤醒。 在这种情况下,被唤醒的线程不应该自己调用close()。

 

被唤醒的线程需要找到一种方法来区分错误是由连接(例如网络错误)生成的,需要它调用close(),还是错误是由其他线程在其上调用close()生成的,在这种情况下,它应该只是报错而不必执行对套接字的任何进一步操作。

因此,避免这两个问题的最佳方法是调用shutdown()而不是close()shutdown()会使文件描述符仍然可用,因此不会被另一个描述符分配,还会唤醒recv()并使调用recv()的线程可以像处理普通错误一样正常关闭套接字。


3
我不了解Solaris网络堆栈实现,但我会提出我的理论/解释为什么它应该是安全的。
  • 线程A进入某个阻塞系统调用,比如说read(2),对于给定的套接字。套接字接收缓冲区中没有数据,因此将线程A从处理器上移开,并将其放到等待此套接字的等待队列上。这里没有启动任何网络堆栈事件,连接状态(假设TCP)也没有改变。
  • 线程B在套接字上发出close(2)。虽然内核套接字结构应该在线程B访问它时被锁定,但没有其他线程持有该锁(当线程A被放置到睡眠等待时,它释放了锁)。假设套接字发送缓冲区中没有未完成的数据,则会发送一个FIN数据包并使连接进入FIN WAIT 1状态(这里再次假设TCP,请参见连接状态图
  • 我猜测,套接字连接状态更改会为所有阻塞在给定套接字上的线程生成唤醒。也就是说,线程A将进入可运行状态,并发现连接正在关闭。如果另一端没有发送自己的FIN,则等待可能会重新进入,否则系统调用将返回eof

无论如何,内部内核结构都将受到保护,以防止不适当的并发访问。这并不意味着从多个线程进行套接字I/O是一个好主意。我建议研究非阻塞套接字、状态机和框架,例如libevent


3

对我来说,在 Linux 中从另一个线程 shutdown() 套接字就可以完成工作。


3
欢迎来到stackoverflow.com。你可以通过提供某种文档参考或举出短小的例子来改进你的回答。一个仅对自己管用的答案是没有帮助的,因为它可能对他人无效。 - stefan

1
如果一个线程在执行recv()send()时,这个socket被另一个线程关闭了,那么这个线程将会收到一个错误。然而,在收到错误后,很难检测正确的补救措施。这是因为与该socket相关联的文件描述符号可能已经被另一个线程占用,此时被唤醒的线程通过一个“有效”的socket接收到了一个错误信息。在这种情况下,被唤醒的线程不应该自己去调用close()方法。
被唤醒的线程需要一些方式来区分这个错误是由连接本身(例如网络错误)引起的,需要调用close()方法;还是由另一个线程在它上面调用close()方法引起的,则应该只是报告错误,而不做任何进一步处理。

0

是的,从另一个线程关闭套接字是可以的。任何正在使用该套接字的阻塞/忙碌线程将报告适当的错误。


11
在Linux中,至少看起来不会报告那样的情况。即使我们从另一个线程调用关闭操作,被阻塞的接收线程仍然保持阻塞状态。 - Jay

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