通过接受关闭另一个线程中使用的套接字有啥风险?

Posted

技术标签:

【中文标题】通过接受关闭另一个线程中使用的套接字有啥风险?【英文标题】:what's the risk of closing socket which is used in another thread by accept?通过接受关闭另一个线程中使用的套接字有什么风险? 【发布时间】:2015-01-30 16:04:47 【问题描述】:

我有一个服务器应用程序。

服务器正在一个线程中接受来自客户端的连接:

 while( (client_sock = accept(socket_desc, (struct sockaddr *)&client, (socklen_t*)&c)) )

     .....
 

我有另一个线程在我的应用程序的出口处执行。在这个线程中,我关闭了套接字socket_desc

close(socket_desc);

在一个线程中关闭一个套接字并且有另一个线程在同一个套接字上接受的风险是什么?

【问题讨论】:

在使用线程时,您应该始终同步对共享数据的访问——您有什么理由认为socket_desc 是一个例外吗? OT:这个(socklen_t*)&c 看起来不太好。将c 声明为socklen_t,这样就不需要这种危险的指针转换了。 确实,对accept() 的调用的错误检查完全缺失,因为它在失败时返回-1。所有值>=0 都是有效的。 【参考方案1】:

我可能是错的。但是,它可能适用于这样的场景:- 考虑 3 个线程 - A、B、C。

    线程 A 在套接字上等待并进入睡眠状态 线程 B 关闭套接字 线程 C 创建了一个新的套接字,它恰好获得了与最近关闭的套接字相同的文件描述符编号(套接字也是一个文件) 线程 A 被唤醒(错误代码在 accept 上)然后调用 close 思考意外错误 => 这会影响在 C 中创建的完全不同的有效套接字!!

【讨论】:

【参考方案2】:

据我所知,您描述的操作组合的语义没有定义,这应该足以让您找到替代方案。我推测合理可能观察到的行为包括

close() 快速返回,并且 accept() 调用很快失败,可能表示EBADFEINVALENOTSOCK 错误,或者 accept() 调用继续阻塞,直到连接请求到达,或者 accept() 调用无限期阻塞;或 close() 阻塞直到accept() 返回,如果有的话;或 close()accept() 死锁。

如果确实没有定义语义,那么几乎任何事情都可能发生。

如果另一个线程必须关闭套接字而不是其上的accept()ing 连接,那么明智的做法是设置某种标志来指示程序正在退出,然后设置signal() accept()ing线程将其从accept() 中分离出来。接收到这样一个信号的线程会从程序退出标志知道停止而不是再次尝试accept()

如果您的线程是可取消的,那么全局标志可以采用线程取消消息的形式。 accept() 函数是一个取消点,因此您的线程将在下次调用 accept() 之前收到取消消息。

【讨论】:

accept 调用之前使用要测试的标志的方法无法工作,因为您无法预测何时会发生线程切换。如果它发生在读取标志之后并且在深入了解accept 的内部之前发生,那么您将遇到与没有标志相同的问题。 调用select() 而不是accept() 可能更简单,并让退出线程写入select() 正在等待的管道,因此服务器线程可以处理close() 到完全避免这个问题。 @harper,当然,如果您将标志实现为变量,则必须适当地同步对它的访问。但是,如果您在signal() 线程之前设置标志以将它们从accept() 中分离出来,如上所述,那么您可以确定它们在循环返回时会看到标志。 (即,必须在循环体之前和循环体中测试变量。)如果您通过线程取消实现标志,那么您甚至不需要搞乱显式同步。 @PaulGriffiths,让服务器线程阻塞在select() 而不是accept() 将提供一种更干净 的方式来中断服务器线程,而不是发出信号。这是一个可靠的方法,我没有考虑过。不过,我不相信它实际上会更容易实施。【参考方案3】:

您可以关闭套接字以中止接受功能。但是必须改代码,仔细检查API函数的返回值。您尝试 while 循环将不起作用,因为它不检查 SOCK_ERROR (-1)。

【讨论】:

这是否记录在任何地方? 小心,@harper:首先,Linux 不是 MS Windows,两者的套接字 API 不同!其中一个区别是您提到的这个常量SOCK_ERROR,它只存在于 WinSock API 中。也就是说,我知道简单地关闭套接字至少在 win32 上有效,但它是否也适用于 POSIX 系统是值得怀疑的。

以上是关于通过接受关闭另一个线程中使用的套接字有啥风险?的主要内容,如果未能解决你的问题,请参考以下文章

在另一个线程上接收的套接字上触发 EAGAIN

从另一个线程向 uWebSockets 0.15.x 套接字发送数据

如何在套接字关闭时唤醒 select()?

套接字在 32739 次连接后不会关闭

套接字关闭时取消阻止 recvfrom

关闭套接字连接