【问题标题】:Windows named pipe: detect in Python on writer side when reader has closed its end without having to write dataWindows命名管道:当阅读器关闭其末端而无需写入数据时,在编写器端检测Python
【发布时间】:2019-04-10 08:41:32
【问题描述】:

我的应用程序将数据流式传输(写入)到命名管道,在调用应用程序时将管道名称指定为 CLI 参数。流数据是不规则的,其中可能没有任何数据可以通过管道发送。

我想检测从管道读取的其他进程何时结束,以便快速释放我的应用程序为流式传输分配的资源。我现在的问题是甚至检测到管道的读取端已经关闭,而没有向管道中写入任何内容。

由于流数据格式是固定的并且不允许空写入或 ping,即使我没有要流式传输的内容,我也不能简单地尝试写入一些管道数据以查看管道读取器是否仍在读取。

我在 Linux 上有一个可行的解决方案,不幸的是它在 Windows 上不起作用,因为 Windows 命名管道不能像在 Linux 上一样在 select() 中统一处理。在 Linux 上,我只需检查管道的写入端是否可以读取,因为这表示管道错误,然后关闭管道并释放我分配的资源。

在 Windows 上,这是不可能的。我已经打开了这样写的管道:

fifo = open('//./pipe/somepipe', 'wb')

从管道尝试fifo.read() 不起作用(正如预期的那样)并立即抛出OSException

正如我所说,我不能尝试一些空/nul 写入; fifo.write(b'') 什么都不做,甚至根本不戳管道以获得可写性。

在 Windows 上是否有任何方法可以测试命名管道的写入端以查看读取器(客户端)是否仍然连接?

【问题讨论】:

  • 我假设您在 Windows 中打开“//./pipe/”。无论如何,如果您直接调用 WINAPI WriteFile,您的想法就会奏效。如果管道正在关闭,这将失败并显示ERROR_NO_DATA (232)。
  • 另外,如果你熟悉NT API,你可以直接查询这个,不用空写。致电NtQueryInformationFile 获取FilePipeLocalInformation。 Python open(特别是 WINAPI CreateFile)总是请求读取属性访问,因此即使它是入站管道(即客户端只有写入数据访问权限),我们也可以在客户端读取此信息。 NamedPipeState 将是 FILE_PIPE_CLOSING_STATE (4)。
  • @eryksun:听起来很有趣,会试试这个;目前我找到了一种解决方法,即外部应用程序协议有一个扩展点,我可以通过编写一个特殊记录来重用它来检查管道状态,接收应用程序会默默地忽略它。

标签: python windows named-pipes


【解决方案1】:

正如@eryksun 上面指出的那样,实际上可以直接使用Win32 API WriteFile 编写一个探测零长度字节字符串:如果管道关闭,那么这将返回“不成功”(false/0 ),如果管道是活动的,则“成功”(true/!=0)。正是我所要求的。因为我发现这比使用NtQueryInformationFile 更简单,所以我现在使用的是空写方法;这是一个简化的例子:

import ctypes
from ctypes import byref, c_ulong, c_char_p
import msvcrt
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

fifo = open('//./pipe/somepipe', 'wb')
data = b''
written = c_ulong(0)
if not kernel32.WriteFile(
        msvcrt.get_osfhandle(fifo.fileno()),
        c_char_p(data), 0, 
        byref(written), 
        None):
    last_error = ctypes.get_last_error()
    if last_error in (
            0x000000E8,  # ERROR_NO_DATA
            # enable as required: 0x000000E9,  # ERROR_PIPE_NOT_CONNECTED
    ):
        # pipe broken
        pass
    else:
        # something else is wrong...
        pass
else:
    # pipe still okay
    pass

有用的资源:

【讨论】:

  • 我建议将其更改为使用kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)。如果kernel32.WriteFile 失败,则获取last_error = ctypes.get_last_error()。如果是 last_error != ERROR_NO_DATA,则通过 raise ctypes.WinError(last_error) 引发异常。否则,您将掩盖意外错误。
  • 另外,使用ctypes.WinDLL 将您的包与其他 Python 库隔离开来。 ctypes.windll 从一开始就是个坏主意。它缓存WinDLL 实例,这些实例缓存函数指针。因此,使用 windll 的包可以在常用 API 函数上设置自定义 argtypesrestype,这可能会破坏需要相同功能的其他包的同时使用。以前在 colorama 和 pyreadline 中也发生过这种情况,它们都使用 Windows 控制台 API。
  • 哦,非常感谢!更新了我的示例代码;我从我的工作代码中发现,在我的情况下,我不会看到管道 关闭,而只是 关闭,所以检查 0xe8 和 0xe9 (@987654338 @ 以及 ERROR_PIPE_NOT_CONNECTED)。由于我们管道是同步阻塞打开的,所以我们知道ERROR_PIPE_NOT_CONNECTED表示管道已经关闭。
  • ERROR_PIPE_NOT_CONNECTED 表示服务器通过DisconnectNamedPipe断开管道实例。查询会说管道状态是FILE_PIPE_DISCONNECTED_STATE。服务器可能已关闭实例的末端,也可能未关闭。更有可能它只是断开连接,并将重复使用该实例进行下一次连接。
  • 很高兴知道;在我的情况下,服务器不会重新连接(这是在服务器和客户端之间的整体协议中定义的)。
猜你喜欢
  • 1970-01-01
  • 2012-08-03
  • 1970-01-01
  • 1970-01-01
  • 2020-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-20
相关资源
最近更新 更多