具有多个读取器的 Unix 上的命名管道 (FIFO)

Posted

技术标签:

【中文标题】具有多个读取器的 Unix 上的命名管道 (FIFO)【英文标题】:Named Pipes (FIFOs) on Unix with multiple readers 【发布时间】:2009-10-28 00:46:33 【问题描述】:

我有两个程序,Writer 和 Reader。

我有一个从 Writer 到 Reader 的 FIFO,所以当我在 Writer 中向 stdin 写入内容时,它会从 Reader 打印到 stdout。

我尝试在打开两个 Reader 的情况下执行此操作,但我只从两个 Reader 程序中的一个将输出输出到 stdout。每次我运行它时,Unix 选择从哪个 Reader 程序打印 stdout 似乎是任意的,但是一旦它选择了其中一个程序,stdout 的每个输出都会从同一个 Reader 程序打印出来。

有人知道为什么会这样吗?

如果我有两个 WRITER 程序,它们都可以写入同一个管道。

【问题讨论】:

您是否想知道为什么数据没有“广播”给每个读者,或者为什么数据没有在每个读者之间均匀分布? 我相信 Writer 正在写入其标准输出(不是标准输入),即 FIFO;每个 Reader 大概都是从自己的标准输入(即 FIFO)读取数据,然后将数据写入自己的标准输出。 Jacob – 我想知道为什么数据不会同时发送给两个读者,只有一个。 +1 ,这很好在 SO 上存档。去年,当 Linux 成为默认开发平台时,我的一些同事也问过我同样的问题。 【参考方案1】:

FIFO中的O表示“out”。一旦您的数据“出局”,它就消失了。 :-) 因此,如果另一个进程出现并且其他人已经发出读取,那么数据不会出现两次。

要完成您的建议,您应该查看 Unix 域套接字。手册页here。您可以编写一个可以写入客户端进程的服务器,绑定到文件系统路径。另请参阅socket()bind()listen()accept()connect(),所有这些都可以与PF_UNIXAF_UNIXstruct sockaddr_un 一起使用。

【讨论】:

【参考方案2】:

Linux tee() 可能适合您的需要。 看这里tee

注意:这个函数是 Linux 特有的。

【讨论】:

tee 不是 linux 特定的 opengroup.org/onlinepubs/9699919799/utilities/tee.html;但我不确定它是否有助于原始问题中的用例 我指的是 C 函数 tee,而不是 command/ultitily tee。但是是的,我不确定这个功能是否在其他平台/库上实现。 三通很棒。困难的部分是,在您将数据流发送到 30 个进程之后,它们可以每个处理 1/30 的数据...您如何组合结果...为此您需要 1 个阅读器和许多作者。诀窍是制作 30 个 fifo,并让读者“选择”它们,读取整个输出。 HADOOP 应该为您这样做,但它是一个糟糕的、臃肿的框架。像 0mq 这样的工具做轻量级/干净的 IPC,适用于大多数语言。 如果您在阅读器上使用 tee,是否至少最后一个阅读器不需要消费数据,否则数据永远不会被消费?【参考方案3】:

我认为您观察到的行为绝非巧合。考虑这个跟踪,它使用“sed”作为两个读取器,一个循环作为写入器:

Osiris JL: mkdir fifo
Osiris JL: cd fifo
Osiris JL: mkfifo fifo
Osiris JL: sed 's/^/1: /' < fifo &
[1] 4235
Osiris JL: sed 's/^/2: /' < fifo &
[2] 4237
Osiris JL: while read line ; do echo $line; done > fifo < /etc/passwd
1: ##
1: # User Database
1: #
1: # Note that this file is consulted directly only when the system is running
1: # in single-user mode. At other times this information is provided by
1: # Open Directory.
1: #
1: # This file will not be consulted for authentication unless the BSD local node
1: # is enabled via /Applications/Utilities/Directory Utility.app
1: #
1: # See the DirectoryService(8) man page for additional information about
1: # Open Directory.
1: ##
1: nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false
1: root:*:0:0:System Administrator:/var/root:/bin/sh
1: daemon:*:1:1:System Services:/var/root:/usr/bin/false
1: _uucp:*:4:4:Unix to Unix Copy Protocol:/var/spool/uucp:/usr/sbin/uucico
1: _lp:*:26:26:Printing Services:/var/spool/cups:/usr/bin/false
2: _postfix:*:27:27:Postfix Mail Server:/var/spool/postfix:/usr/bin/false
2: _mcxalr:*:54:54:MCX AppLaunch:/var/empty:/usr/bin/false
2: _pcastagent:*:55:55:Podcast Producer Agent:/var/pcast/agent:/usr/bin/false
2: _pcastserver:*:56:56:Podcast Producer Server:/var/pcast/server:/usr/bin/false
2: _serialnumberd:*:58:58:Serial Number Daemon:/var/empty:/usr/bin/false
2: _devdocs:*:59:59:Developer Documentation:/var/empty:/usr/bin/false
2: _sandbox:*:60:60:Seatbelt:/var/empty:/usr/bin/false
2: _mdnsresponder:*:65:65:mDNSResponder:/var/empty:/usr/bin/false
2: _ard:*:67:67:Apple Remote Desktop:/var/empty:/usr/bin/false
2: _www:*:70:70:World Wide Web Server:/Library/WebServer:/usr/bin/false
2: _eppc:*:71:71:Apple Events User:/var/empty:/usr/bin/false
2: _cvs:*:72:72:CVS Server:/var/empty:/usr/bin/false
2: _svn:*:73:73:SVN Server:/var/empty:/usr/bin/false
2: _mysql:*:74:74:MySQL Server:/var/empty:/usr/bin/false
2: _sshd:*:75:75:sshd Privilege separation:/var/empty:/usr/bin/false
2: _qtss:*:76:76:QuickTime Streaming Server:/var/empty:/usr/bin/false
2: _cyrus:*:77:6:Cyrus Administrator:/var/imap:/usr/bin/false
2: _mailman:*:78:78:Mailman List Server:/var/empty:/usr/bin/false
2: _appserver:*:79:79:Application Server:/var/empty:/usr/bin/false
2: _clamav:*:82:82:ClamAV Daemon:/var/virusmails:/usr/bin/false
2: _amavisd:*:83:83:AMaViS Daemon:/var/virusmails:/usr/bin/false
2: _jabber:*:84:84:Jabber XMPP Server:/var/empty:/usr/bin/false
2: _xgridcontroller:*:85:85:Xgrid Controller:/var/xgrid/controller:/usr/bin/false
2: _xgridagent:*:86:86:Xgrid Agent:/var/xgrid/agent:/usr/bin/false
2: _appowner:*:87:87:Application Owner:/var/empty:/usr/bin/false
2: _windowserver:*:88:88:WindowServer:/var/empty:/usr/bin/false
2: _spotlight:*:89:89:Spotlight:/var/empty:/usr/bin/false
2: _tokend:*:91:91:Token Daemon:/var/empty:/usr/bin/false
2: _securityagent:*:92:92:SecurityAgent:/var/empty:/usr/bin/false
2: _calendar:*:93:93:Calendar:/var/empty:/usr/bin/false
2: _teamsserver:*:94:94:TeamsServer:/var/teamsserver:/usr/bin/false
2: _update_sharing:*:95:-2:Update Sharing:/var/empty:/usr/bin/false
2: _installer:*:96:-2:Installer:/var/empty:/usr/bin/false
2: _atsserver:*:97:97:ATS Server:/var/empty:/usr/bin/false
2: _unknown:*:99:99:Unknown User:/var/empty:/usr/bin/false
Osiris JL:  jobs
[1]-  Running                 sed 's/^/1: /' < fifo &
[2]+  Done                    sed 's/^/2: /' < fifo
Osiris JL: echo > fifo
1: 
Osiris JL: jobs
[1]+  Done                    sed 's/^/1: /' < fifo
Osiris JL: 

如您所见,两位读者都必须阅读一些数据。在任何时候安排哪个阅读器取决于操作系统的心血来潮。请注意,我小心地使用了 echo 来打印文件的每一行;那些是原子读取的原子写入。

如果我使用 Perl 脚本,例如在读取和回显一行后有延迟,那么我很可能会看到(通常)阅读器 1 的两行对应阅读器 2 的每 1 行的更确定的行为。

perl -n -e 'while(<>) print "1: $_"; sleep 1; ' < fifo &
perl -n -e 'while(<>) print "2: $_"; sleep 2; ' < fifo &

在 MacOS X 10.5.8 (Leopard) 上完成的实验 - 但可能与大多数地方相似。

【讨论】:

哦,值得一提的是,当我在两个阅读器脚本中尝试使用“睡眠 1”的 Perl 变体时,阅读器 2 在其中一次运行中处理了所有内容。我将非对称睡眠放在适当的位置以强制系统进行操作。 这很有趣......似乎在一个读取器从 FIFO 读取后,数据被擦除,因此另一个读取器无法读取相同的数据。 当然——一旦数据被读取,它就被消耗掉了。这才是重点。与终端相同 - 如果多个进程正在竞争数据,则一个得到它而另一个没有。对于一些混淆,请尝试执行 'more somebigfile |更多”。【参考方案4】:

我想补充一下上面的解释,即写入(和推测的读取,虽然我无法从手册页确认这一点)到管道的原子大小达到一定大小(Linux 上为 4KiB)。所以假设我们从一个空管道开始,写入器将

a) 写入器一次性写入所有数据。虽然发生这种情况,但没有其他进程有机会读取(或写入)管道。

b) 其中一位读者被安排进行 I/O。

c) 选择的阅读器一次性读取管道中的所有数据,并在稍后将它们打印到其标准输出。

我认为这可以解释您只看到一位读者的输出。尝试以较小的块写入,并且可能在每次写入后休眠。

当然,其他人已经回答了为什么每个数据只能由进程读取。

【讨论】:

编写器一次性写入所有数据。虽然发生这种情况,但没有其他进程有机会读取(或写入)管道。 在 Linux 中绝对是错误的。写入器将阻塞,直到读取器从管道中读取。所以读取和写入总是在时间上重叠(但不会破坏原子性)。【参考方案5】:

套接字解决方案有效,但如果服务器崩溃,就会变得复杂。为了允许任何进程成为服务器,我在包含对给定文件的位置/长度/数据更改的临时文件的末尾使用记录锁。我使用临时命名管道将附加请求传达给在临时文件末尾具有写锁的任何进程。

【讨论】:

以上是关于具有多个读取器的 Unix 上的命名管道 (FIFO)的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Unix(或 Windows)中的(最好是未命名的)管道将一个进程的标准输出发送到多个进程?

如何避免命名管道中的多个作者?

在多个进程写入时读取命名管道

ifstream 管道和多个(顺序)编写器

Windows上的PHP命名管道

当阅读器断开连接时,命名管道 (FIFO) 数据会去哪里?