检测阅读器何时关闭命名管道(FIFO)

Posted

技术标签:

【中文标题】检测阅读器何时关闭命名管道(FIFO)【英文标题】:Detect when reader closes named pipe (FIFO) 【发布时间】:2015-02-04 07:08:27 【问题描述】:

作者有什么方法可以知道读者已经关闭了命名管道的末端(或退出),没有写入它?

我需要知道这一点,因为我写入管道的初始数据不同;读者期望在其余数据到来之前有一个初始标题。

目前,当我的write()EPIPE 失败时,我会检测到这一点。然后我设置了一个标志,上面写着“下一次,发送标题”。但是,读者可以在我写任何东西之前关闭并重新打开管道。在这种情况下,我从来没有意识到他做了什么,也没有发送他期望的标题。

这里有什么异步事件类型的东西可以提供帮助吗?我没有看到任何信号正在发送。

请注意,我没有包含任何语言标签,因为这个问题应该被认为与语言无关。我的代码是 Python,但答案应该适用于 C 或任何其他具有系统调用级绑定的语言。

【问题讨论】:

男人 2 写:EPIPEfd is connected to a pipe or socket whose reading end is closed. When this happens the writing process will also receive a SIG‐ PIPE signal. (Thus, the write return value is seen only if the program catches, blocks or ignores this signal.) 有什么理由不定期发送标头数据?如果读者可以处理它们,也许它可以处理冗余标题? @wildplasser 我不确定你现在的意思。是的,当我尝试写入时,我知道管道已关闭。我的问题是问如何在不写入的情况下异步检测到这一点。 @wallyk 这不是一个选项。在这种情况下,管道的另一端是 Wireshark,它期待 pcap 数据。初始标头告诉 Wireshark 数据类型,而其余数据是数据包。 【参考方案1】:

如果您使用基于poll 系统调用的事件循环,您可以使用包含EPOLLERR 的事件掩码注册管道。在 Python 中,使用select.poll

import select
fd = open("pipe", "w")
poller = select.poll()
poller.register(fd, select.POLLERR)
poller.poll()

将等到管道关闭。

要对此进行测试,请运行 mkfifo pipe,启动脚本,然后在另一个终端中运行,例如 cat pipe。一旦退出cat 进程,脚本就会终止。

【讨论】:

这里的poller 是什么? 对不起,我忘了添加那行。这是一个select.poll 对象。 第二个参数应该是select.POLLERR,而不是select.EPOLLERR。另外,你真的试过这个吗?我对select (below) 的测试表明,当另一端关闭时,管道出人意料地变得可读,而不是异常 我已经将赏金奖励给了你,因为你的回答让我走上了正轨。我会等到我有一个使用 select.poll 对象的工作示例,然后接受您的回答。 确实,我混淆了epoll and poll constants,但幸运的是这两个常量的值都是8。我已经解决了这个问题,还有另一个错字。无论如何,POLLERR 是正确的 - poll() API 设置错误状态,而 select() API 报告可读性,我不知道为什么会有差异,可能是出于历史原因。【参考方案2】:

奇怪的是,当最后一个阅读器关闭管道时,select 表示管道是可读的:

writer.py

#!/usr/bin/env python
import os
import select
import time

NAME = 'fifo2'

os.mkfifo(NAME)


def select_test(fd, r=True, w=True, x=True):
    rset = [fd] if r else []
    wset = [fd] if w else []
    xset = [fd] if x else []

    t0 = time.time()
    r,w,x = select.select(rset, wset, xset)

    print 'After 0 sec:'.format(time.time() - t0)
    if fd in r: print ' 0 is readable'.format(fd)
    if fd in w: print ' 0 is writable'.format(fd)
    if fd in x: print ' 0 is exceptional'.format(fd)

try:
    fd = os.open(NAME, os.O_WRONLY)
    print '0 opened for writing'.format(NAME)

    print 'select 1'
    select_test(fd)

    os.write(fd, 'test')
    print 'wrote data'

    print 'select 2'
    select_test(fd)

    print 'select 3 (no write)'
    select_test(fd, w=False)

finally:
    os.unlink(NAME)

演示:

1 号航站楼:

$ ./pipe_example_simple.py
fifo2 opened for writing
select 1
After 1.59740447998e-05 sec:
 3 is writable
wrote data
select 2
After 2.86102294922e-06 sec:
 3 is writable
select 3 (no write)
After 2.15910816193 sec:
 3 is readable

2 号航站楼:

$ cat fifo2
test
# (wait a sec, then Ctrl+C)

【讨论】:

【参考方案3】:

没有这样的机制。通常,根据 UNIX 方式,在任何一端都没有打开或关闭流的信号。这只能通过读取或写入它们来检测(相应地)。

我会说这是错误的设计。目前,您正试图通过打开管道让接收器发出信号接收它们的可用性。因此,要么您以适当的方式实现此信号,要么将“关闭逻辑”合并到管道的发送部分。

【讨论】:

"There is no such mechanism." 看到我上面的回答,明显说明你错了吗? pollselect 调用用于检测 FD 何时准备好进行读/写,以避免阻塞。仍然没有关于流打开或关闭的信号信息,为了检测到这一点,您需要执行读/写或 test 以了解读/写准备情况。我承认这些信息被遗漏了。尽管如此,答案并没有改变,因为轮询仍然不是一种信号机制,并且您描述的竞争条件(在轮询/读取/写入之前关闭和重新打开管道)仍然存在 poll。它可以在您投票之前重新打开。

以上是关于检测阅读器何时关闭命名管道(FIFO)的主要内容,如果未能解决你的问题,请参考以下文章

IPC - 命名管道(fifo)- 使用

Bash 将 stdio 重定向到命名管道

IPC - 命名管道(fifo)- 使用

IPC - 命名管道(fifo)- 使用

IPC - 命名管道(fifo)- 使用

如何避免回显关闭 FIFO 命名管道? - Unix FIFO 的有趣行为