打破 ReadFile() 阻塞 - 命名管道 (Windows API)

Posted

技术标签:

【中文标题】打破 ReadFile() 阻塞 - 命名管道 (Windows API)【英文标题】:Breaking ReadFile() blocking - Named Pipe (Windows API) 【发布时间】:2010-10-10 05:46:40 【问题描述】:

为了简化,这是一个 NamedPipe SERVER 正在等待 NamedPipe CLIENT 写入管道的情况(使用 WriteFile())

阻塞的 Windows API 是 ReadFile()

服务器已创建启用阻塞的同步管道(无重叠 I/O)

客户端已经连接,现在服务器正在等待一些数据。

在正常的事情流程中,客户端发送一些数据,服务器处理它,然后返回到ReadFile()等待下一个数据块。

同时发生事件(例如用户输入),NamedPipe SERVER 现在必须执行一些其他代码,而在 ReadFile() 阻塞时它无法执行。

此时我需要提一下,NamedPipe Client 不是我的应用程序,所以我无法控制它。我不能让它发送几个字节来解除对服务器的阻塞。它只是坐在那里不发送任何数据。由于我无法控制客户端实现,因此我无法对此进行任何更改。

一种解决方案是创建一个单独的线程,在其中执行所有 ReadFile() 操作。这样当事件发生时,我就可以处理代码。问题在于该事件还需要一个单独的线程,所以现在我为该服务器的每个实例添加了两个额外的线程。由于这需要可扩展,因此这是不可取的。

从我尝试调用的另一个线程

 DisconnectNamedPipe()

 CloseHandle()

它们都不会返回(直到客户端写入管道。)

我无法连接到同一个管道并写入几个字节,因为:

“命名管道的所有实例共享相同的管道名称,但每个实例都有 它自己的缓冲区和句柄,并为客户端/服务器提供单独的管道 沟通。”

http://msdn.microsoft.com/en-us/library/aa365590.aspx

我需要一种方法来伪造它,所以 64,000 美元的问题是:

如何打破 ReadFile() 的阻塞?

【问题讨论】:

【参考方案1】:

在 ReadFile 之前试试这个:

BOOL WINAPI PeekNamedPipe(
  __in       HANDLE hNamedPipe,
  __out_opt  LPVOID lpBuffer,
  __in       DWORD nBufferSize,
  __out_opt  LPDWORD lpBytesRead,
  __out_opt  LPDWORD lpTotalBytesAvail,
  __out_opt  LPDWORD lpBytesLeftThisMessage
);

if(TotalBytesAvail > 0)
  ReadFile(....);

-AV-

【讨论】:

这就像宣传的那样工作,但假设管道中有要读取的数据。问题是我们需要 ReadFile() 来阻止直到有数据发送。然后我们读取数据,回到ReadFile()的阻塞状态。如果我们不使用 ReadFile(0 的阻塞,那么我们将需要不断检查管道(首先破坏了阻塞的目的)【参考方案2】:

看看 CancelSynchronousIo

标记待处理的同步 I/O 发出的操作 指定线程已取消。

还有 CancelIo/CancelIoEx:

取消所有挂起的异步 I/O 操作,请使用:

CancelIo——这个函数只取消 调用发出的操作 指定文件句柄的线程。

CancelIoEx — 这个函数取消所有 线程发出的操作 指定的文件句柄。

http://msdn.microsoft.com/en-us/library/aa363794(VS.85).aspx http://msdn.microsoft.com/en-us/library/aa365467(VS.85).aspx

【讨论】:

哦。我错过了... 最低支持的客户端 Windows Vista 最低支持的服务器 Windows Server 2008 不幸的是,这是 Windows Server 2003。该死 Google 获取“msdn 同步和异步 I/O”一文。似乎剩下的唯一选择是 TerminateThread,但这不是一个好主意(谷歌的'msdn TerminateThread 可能导致以下问题') 更多信息在这里:msdn.microsoft.com/en-us/library/aa480216.aspx(“Windows Vista 中的 Win32 I/O 取消支持”)。【参考方案3】:

迈克,

您无法取消同步 ReadFile。但是您可以切换到异步(重叠)操作。通过这样做,您可以实现一个非常可扩展的架构。

可能的算法(只是一个想法):

对于每个新的客户端调用 ReadFile WaitForMultipleObjects 句柄重叠。hEvent + 你的 自定义事件 迭代信号事件,并安排它们由线程池中的线程执行。

这样你就可以只有很少的线程来接收连接和读取数据,而实际的数据处理可以由线程池来完成。

【讨论】:

是的。那是设计的下一个阶段。不幸的是,我继承了这个问题的大部分。 IPC 不对我开放,FastCGI 规范也不对我开放。我是一个远射,但我想我会问,以防有人有打破障碍的技术。【参考方案4】:

问题在于, 事件还需要一个单独的线程, 所以现在我有两个额外的线程 对于此服务器的每个实例。 由于这需要可扩展,因此 是不可取的。

在我的职业生涯中,我从未发现“更多线程”==“可扩展性更低”。你有多少这样的“服务器”实例?

通常,如果该操作将被阻塞,并且系统需要在操作被阻塞时做出响应,则需要在单独的线程中执行该操作。

【讨论】:

您有多少个这些“服务器”实例?他们正在谈论高达 10k ......我知道开销很低,但我们的想法是尽量减少这一点。我只是在问这个问题……有可能吗? 是的,这需要一个线程池,但关键是与启动一个新线程相关的性能开销,并且每个线程还为其堆栈分配了一些内存等。这加起来就是不受欢迎。 首先,您不需要杀死池线程。让它睡觉,直到下次需要。看看这篇文章msdn.microsoft.com/en-us/library/…(这是.Net的,但你可以借鉴)【参考方案5】:

如果异步 I/O 操作使用 I/O 完成端口,则它们不必阻塞任何线程。见:http://msdn.microsoft.com/en-us/library/aa365198(VS.85).aspx

【讨论】:

【参考方案6】:

发生的情况是,当您的客户端尝试连接到服务器入站管道(不再存在)时,服务器出站管道处于打开状态等待连接......您需要做的是清除您的出站管道为了循环回您的入站。您可以通过读取文件在客户端刷新(记住循环连接建立,因为那里有一个“握手”,它永远不会第一次工作)

【讨论】:

这个问题大家都知道8年了,为什么现在才回答?【参考方案7】:

只需使用SetNamedPipeHandleState 函数 https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-setnamedpipehandlestate

调用此函数时使用PIPE_NOWAIT 标志。

hNamedPipe 应该是从CreateFile 函数返回的句柄。

之后,当没有可用数据时,对ReadFile的调用将不会阻塞线程。

【讨论】:

以上是关于打破 ReadFile() 阻塞 - 命名管道 (Windows API)的主要内容,如果未能解决你的问题,请参考以下文章

命名管道:ConnectNamedPipe 后的 ReadFile 返回 ERROR_BROKEN_PIPE

命名管道问题

使用 ReadFile 异步读取管道

如何解除阻塞已删除命名管道上的线程阻塞?

非阻塞命名管道

从命名管道读取不会给出任何输出并无限期地阻塞代码