在学习 Richard Stevens 的《Unix 网络编程》一书时,我遇到了以下几行内容,其中谈到在客户端和服务器之间使用 FIFO。
客户端进程启动,它们打开 FIFO 进行写入,写入其请求,然后退出。发生的情况是每次客户端进程终止时读取都会向守护进程返回零。然后,守护进程必须再次打开 FIFO(只读),并在此等待,直到客户端进程打开它进行写入。
我不明白最后一行。为什么服务器进程必须再次打开 FIFO,它只需要在客户端进程写入后再次读取,对吧?
答案1
为什么服务器进程必须再次打开 FIFO,它只需要在客户端进程写入后再次读取,对吗?
有趣的是,让我们尝试一下你的建议。以下结果是在 Linux 4.9.0-6-amd64(Ubuntu Linux 内核)上生成的。
$ mkfifo t
$ (cat; cat) < t & # run a "server" as a background job
[1] 4856
$ echo 1 > t
1
[1]+ Done ( cat; cat ) < t
它没有按我们想要的方式工作。第一个cat
按预期读取 EOF,然后退出。问题是第二个cat
还立即读取 EOF,这样我们的“服务器”就完成了。这是不可能的等待对于新客户端(无需重复调用 read() 并浪费 CPU 时间)。
如果您知道如何在 shell 中操作文件描述符 (FD),我们可以通过另一种方式来帮助确认。
$ echo 1 > t &
$ exec 3 < t # open "t" for reading, as FD 3.
$ cat <&3
1
[1]+ Done echo 1 > t
$ cat <&3
$
$ echo 2 > t &
[1] 5102
$ cat <&3
2
[1]+ Done echo 2 > t
$ cat <&3
$
答案是,重新open()
设置 fifo 的目的是阻止其他人打开它进行写入。如果没有该步骤,所有后续read()
对 fifo 的调用都将立即返回 0 (EOF)。
当我注意到这一点时,我想知道如何systemd-initctl
运作。该程序模拟 systemd 下的旧/dev/initctl
fifo。 (免责声明:测试这一点并不容易;我不会费心记录如何进行)。答案是 systemd-initctl 打开 fifo 以进行读取和写入。 (从技术上讲,是 systemd 根据 systemd-initctl.socket 指定打开 fifo,并将其传递给 systemd-initctl)。打开 fifo 进行同时读写是 Linux 特有的功能。但通过这样做,systemd 正在实现与 Stevens 接下来提到的相同的技巧:
为了避免这种情况,一个有用的技术是守护进程打开 FIFO 两次 - 一次用于读取,一次用于写入。返回用于读取的文件描述符用于读取客户端请求,而用于写入的文件描述符从不使用。通过让 FIFO 始终打开以进行写入(只要守护进程存在),读取不会返回 EOF,而是等待下一个客户端请求。