28

我有一个工作线程正在侦听 TCP 套接字以获取传入流量,并缓冲接收到的数据以供主线程访问(我们称之为套接字A)。但是,即使没有数据进来,工作线程也必须执行一些常规操作(例如每秒一次)。因此,我使用select()超时,这样我就不需要继续轮询。(请注意,调用receive()非阻塞套接字然后休眠一秒钟是不好的:传入的数据应该立即可供主线程使用,即使主线程可能并不总是能够立即处理它,因此需要缓冲。)

现在,我还需要能够立即向工作线程发出信号以执行其他操作;从主线程开始,我需要立即让工作线程select()返回。目前,我已经解决了这个问题(基本上是从这里这里采用的方法):

在程序启动时,工作线程为此目的创建一个额外的数据报 (UDP) 类型的套接字,并将其绑定到某个随机端口(我们称之为套接字B)。同样,主线程创建一个用于发送的数据报套接字。在对 的调用中,工作线程select()现在. 当主线程需要发出信号时,它会发送几个字节到相应的端口 on 。回到工作线程,如果B保留在after返回中,则调用 then 并简单地忽略接收到的字节。fd_setsendto()localhostfd_setselect()recvfrom()

这似乎工作得很好,但我不能说我喜欢这个解决方案,主要是因为它需要为B绑定一个额外的端口,而且还因为它添加了几个额外的套接字 API 调用,我猜这些调用可能会失败——我不真的很想为每个案例找出适当的行动。

我认为理想情况下,我想调用一些以Aselect()作为输入的函数,除了立即返回之外什么都不做。但是,我不知道这样的功能。(我想我可以例如shutdown()套接字,但副作用是不能接受的:)

如果这是不可能的,那么第二好的选择是创建一个比真正的 UDP 套接字更笨的B,并且实际上不需要分配任何有限的资源(超出合理的内存量)。我猜Unix 域套接字可以做到这一点,但是:该解决方案的跨平台性不应该比我目前拥有的少得多,尽管一些适量的#ifdef东西是可以的。(我主要针对 Windows 和 Linux ——顺便写一下 C++。)

请不要建议重构以摆脱两个单独的线程。这种设计是必要的,因为主线程可能会被阻塞很长时间(例如,做一些密集的计算——我不能receive()从最里面的计算循环开始定期调用),同时,有人需要缓冲传入的数据(并且由于我无法控制的原因,它不能是发件人)。

现在我正在写这篇文章,我意识到肯定有人会简单地回复“ Boost.Asio ”,所以我只是第一次看它......虽然找不到明显的解决方案。请注意,我也不能(轻松)影响套接字A的创建方式,但如果需要,我应该能够让其他对象包装它。

4

4 回答 4

36

你快到了。使用“自管”技巧。打开一个管道,将其添加到您的select()read 和 writefd_set中,从主线程写入它以解除阻塞工作线程。它可以跨 POSIX 系统移植。

我在一个系统中看到了一种类似的 Windows 技术变体(实际上与上述方法一起使用,用 分隔#ifdef WIN32)。可以通过添加一个虚拟(未绑定)数据报套接字fd_set然后关闭它来实现解除阻塞。缺点是,当然,您每次都必须重新打开它。

然而,在上述系统中,这两种方法都相当谨慎地使用,并且用于意外事件(例如,信号、终止请求)。首选方法仍然是可变超时时间select(),具体取决于为工作线程安排的时间。

于 2008-12-21T12:26:38.957 回答
3

使用管道而不是套接字更干净一些,因为另一个进程不可能抓住它并将事情搞砸。

使用 UDP 套接字肯定会产生杂散数据包进入和干扰的可能性。

匿名管道将永远不可用于任何其他进程(除非您将其提供给它)。

您也可以使用信号,但在多线程程序中,您需要确保除您想要的线程之外的所有线程都屏蔽了该信号。

于 2008-12-22T07:47:00.627 回答
1

在 unix 上,使用管道会很简单。如果您在 Windows 上并希望继续使用 select 语句来保持您的代码与 unix 兼容,那么创建一个未绑定的 UDP 套接字并关闭它的技巧非常有效且简单。但是你必须使它成为多线程安全的。

我发现使这个多线程安全的唯一方法是在运行 select 语句的同一线程中关闭并重新创建套接字。当然,如果线程在选择上阻塞,这很困难。然后在windows中调用QueueUserAPC。当 windows 在 select 语句中阻塞时,线程可以处理异步过程调用。您可以使用 QueueUserAPC 从不同的线程安排此操作。Windows 中断选择,在同一个线程中执行您的函数,然后继续选择语句。您现在可以在您的 APC 方法中关闭套接字并重新创建它。保证线程安全,您永远不会丢失信号。

于 2012-12-24T13:18:38.873 回答
0

简单来说:一个全局变量保存socket句柄,然后关闭全局socket,select()会立即返回:closesocket(g_socket);

于 2021-12-24T07:57:38.377 回答