检测读者关闭命名管道(FIFO)的情况

9
一个写入者如何知道读取者已关闭或退出命名管道的末端,而不写入它?
我需要知道这个,因为我写入管道的初始数据是不同的。读取者在其余数据到来之前期望一个初始头文件。
目前,当我的write()失败,并返回错误码EPIPE时,我检测到了这一点。然后我设置一个标志表示“下次发送头文件”。但是,读取者在我写入任何内容之前可能会关闭并重新打开管道。在这种情况下,我永远不会意识到他所做的事情,并且不会发送他期望的头文件。
是否有任何类型的异步事件可以帮助解决这个问题?我没有看到任何信号被发送。 请注意,我没有包括任何语言标签,因为这个问题应该被认为是与语言无关的。我的代码是Python,但答案应该适用于C或任何其他具有系统调用级别绑定的语言。

man 2 write: EPIPE fd连接到一个读端已关闭的管道或套接字。 当出现这种情况时,写入进程也将收到SIGPIPE信号。 (因此,仅当程序捕获、阻塞或忽略此信号时,才会看到写入返回值。) - wildplasser
有没有理由不定期发送头数据?如果读取器可以处理它们,也许它可以处理冗余的头部? - wallyk
@wildplasser,我现在不确定你想表达什么。是的,我知道当我尝试写入时管道已关闭。我的问题是如何在不写入的情况下异步检测到这一点。 - Jonathon Reinhart
@wallyk 这不是一个选项。在这种情况下,管道的另一端是 Wireshark,它期望 pcap 数据。初始标头告诉 Wireshark 数据类型,而其余数据则是数据包。 - Jonathon Reinhart
3个回答

4

如果您正在使用基于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 是什么? - Jonathon Reinhart
抱歉,我忘记添加那行了。它是一个 select.poll 对象。 - Phillip
第二个参数应该是 select.POLLERR,而不是 select.EPOLLERR。另外,你真的试过吗?我的测试使用 select(参见下文)表明,当另一端关闭时,管道出奇地变成了可读,而不是异常 - Jonathon Reinhart
我已经授予你赏金,因为你的答案让我找到了正确的方向。不过,在我使用 select.poll 对象并得到一个可行的示例之前,我会等待一下再接受你的答案。 - Jonathon Reinhart
事实上,我混淆了epoll和poll常量,但幸运的是这两个常量都有值为8。我已经修复了这个问题,还有另一个打字错误。无论如何,POLLERR是正确的 - poll() API设置了一个错误状态,而select() API报告可读性,我不知道为什么会有差异,也许是出于历史原因。 - Phillip

3

奇怪的是,当最后一个读取器关闭管道时,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)

1

没有这样的机制。一般来说,按照UNIX方式,在两端都没有用于流打开或关闭的信号。只有通过读取或写入它们(相应地)才能检测到这一点。

我认为这是错误的设计。目前,您正在尝试让接收器通过打开管道来发出可用性信号。因此,要么以适当的方式实现此信号传递,要么在管道发送部分中纳入“关闭逻辑”。


1
"没有这样的机制。" 你看到我之前的回答了吗?它清楚地表明你是错的。 - Jonathon Reinhart
pollselect调用用于检测FD何时准备好进行读/写,以避免阻塞。仍然没有关于流打开或关闭的信号信息,为了检测这一点,您需要执行读/写或测试读/写准备就绪。我承认这些信息被省略了。答案不会改变,因为轮询仍然不是一个信号机制,并且您描述的竞争条件(在轮询/读取/写入之前关闭和重新打开管道)仍然存在于poll中。它可能在您轮询之前重新打开。 - Prikso NAI

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接