IPC 通信之 FIFO
程序员文章站
2024-01-31 16:05:46
...
FIFO 也被称为命名管道,未命名的管道只能在两个相关的进程之间使用,而且这两个相关的进程还要有一个共同的创建了它们的祖先进程。而通过 FIFO,即使不相关的进程也能交换数据。
FIFO 是一种文件类型,通过 stat 结构的 st_mode 成员可以知道文件是否是 FIFO 类型,可以用 S_ISFIFO 宏对此进行测试。创建 FIFO 类似于创建文件,它的路径名也的确存在与文件系统中(POSIX.1 的 XSI 扩展中还可通过 mknod 和 mknodeat 创建 FIFO,也可使用 mkfifo(1) 命令来创建)。
其中的 mode 参数的说明同 open 的 mode 相同。mkfifoat 函数和 mkfifo 相似,但是 mkfifoat 可以用来在 fd 文件描述符表示的目录相关的位置创建一个 FIFO,有以下 3 种情形。
(1)如果 path 参数指定的是绝对路径,则忽略 fd 参数,此时 mkfifoat 和 mkfifo 类似。
(2)如果 path 参数指定的是相对路径,则 fd 参数是一个打开目录的有效文件描述符,路径名和目录有关。
(3)如果 path 参数指定的是相对路径,并且 fd 参数有一个特殊值 AT_FDCWD,则路径名以当前目录开始,mkfifoat 和 mkfifo 类似。
当 open 一个 FIFO,非阻塞标志 O_NONBLOCK 会产生下列影响。
(1)在没有指定该标志的情况下,只读 open 要阻塞到某个其他进程为写而打开这个 FIFO 为止,只写 open 要阻塞到某个其他进程为读而打开它为止。
(2)指定了该标志时,则只读 open 立即返回。但如果没有进程为读而打开一个 FIFO,那么只写 open 将返回 -1,并将 errno 设置成 ENXIO。
类似于管道,若 write 一个尚无进程为读而打开的 FIFO,则产生信号 SIGPIPE。若某个 FIFO 的最后一个写进程关闭了该 FIFO,则将为该 FIFO 的读进程产生一个文件结束标志。一个给定的 FIFO 可能有多个写进程,如果不希望多个进程所写的数据交叉,则必须考虑原子写操作,可被原子地写到 FIFO 的最大数据量也是通过常量 PIPE_BUF 来指定的。
FIFO 有下面两种用途。
(1)shell 命令使用 FIFO 将数据从一条管道传送到另一条时,无需创建中间临时文件。
(2)客户-服务器进程应用程序中,FIFO 用作汇聚点,在客户进程和服务器进程之间传递数据。
下面的两个实例分别说明了这两种用途。
实例一:用 FIFO 复制输出流
FIFO 可用于复制一系列 shell 命令中的输出流,这就防止了将数据写向磁盘文件,类似于使用管道来避免中间磁盘文件。考虑这样一个过程,它需要对一个经过过滤的输入流进行两次处理。可以像下面这样使用 FIFO 和 tee(1) 程序来实现这样的过程而无需使用临时文件(tee 程序将其标准输入同时复制到其标准输出以及其命令行中命名的文件中)。
这里先在后台启动 prog3 来从 FIFO 读数据,然后使用 tee 将 prog1 的输出发送到 FIFO 和 prog2。下图显示了这样的进程安排。
实例二:使用 FIFO 进行客户进程-服务器进程通信
FIFO 的另一个用途是在客户进程和服务器进程之间传送数据。假设有一个服务器进程的每个客户进程都可以将它们的请求写到一个该服务器进程创建的众所周知的 FIFO 中。为避免客户进程的多次写之间的交叉,各客户进程发送给服务器的请求的长度要小于 PIPE_BUF 字节。为了将响应发送给各客户进程,服务器进程不能使用单个 FIFO,因为客户进程不可能知道何时去读它们的相应以及何时响应其他客户进程。一种解决办法是,每个客户进程都在其请求中包含它的进程 ID,然后服务器进程基于每个客户进程的进程 ID 为各个客户创建一个 FIFO,例如服务器进程可以用路径 /tmp/serv1.xxxx 创建 FIFO,其中 xxxx 表示客户进程的进程 ID。下图显示了这种安排。
虽然这种安排可以工作,但服务器进程不能判断一个客户进程是否崩溃终止,这就使得客户进程专用的 FIFO 会遗留在文件系统中。另外,服务器进程还必须得捕捉 SIGPIPE 信号,以免客户在发送请求后还没读取响应就终止了。还有如果服务器进程以只读方式打开众所周知的 FIFO,则每当客户进程个数变为 0 时,服务器进程就将在 FIFO 中读到一个文件结束标志。为使服务器进程免于处理这种情况,一种常用的技巧是使服务器进程以读-写方式打开该 FIFO。
FIFO 是一种文件类型,通过 stat 结构的 st_mode 成员可以知道文件是否是 FIFO 类型,可以用 S_ISFIFO 宏对此进行测试。创建 FIFO 类似于创建文件,它的路径名也的确存在与文件系统中(POSIX.1 的 XSI 扩展中还可通过 mknod 和 mknodeat 创建 FIFO,也可使用 mkfifo(1) 命令来创建)。
#include <sys/stat.h> int mkfifo(const char *path, mode_t mode); int mkfifoat(int fd, const char *path, mode_t mode); /* 两个函数的返回值:若成功,返回 0;否则,返回 -1 */
其中的 mode 参数的说明同 open 的 mode 相同。mkfifoat 函数和 mkfifo 相似,但是 mkfifoat 可以用来在 fd 文件描述符表示的目录相关的位置创建一个 FIFO,有以下 3 种情形。
(1)如果 path 参数指定的是绝对路径,则忽略 fd 参数,此时 mkfifoat 和 mkfifo 类似。
(2)如果 path 参数指定的是相对路径,则 fd 参数是一个打开目录的有效文件描述符,路径名和目录有关。
(3)如果 path 参数指定的是相对路径,并且 fd 参数有一个特殊值 AT_FDCWD,则路径名以当前目录开始,mkfifoat 和 mkfifo 类似。
当 open 一个 FIFO,非阻塞标志 O_NONBLOCK 会产生下列影响。
(1)在没有指定该标志的情况下,只读 open 要阻塞到某个其他进程为写而打开这个 FIFO 为止,只写 open 要阻塞到某个其他进程为读而打开它为止。
(2)指定了该标志时,则只读 open 立即返回。但如果没有进程为读而打开一个 FIFO,那么只写 open 将返回 -1,并将 errno 设置成 ENXIO。
类似于管道,若 write 一个尚无进程为读而打开的 FIFO,则产生信号 SIGPIPE。若某个 FIFO 的最后一个写进程关闭了该 FIFO,则将为该 FIFO 的读进程产生一个文件结束标志。一个给定的 FIFO 可能有多个写进程,如果不希望多个进程所写的数据交叉,则必须考虑原子写操作,可被原子地写到 FIFO 的最大数据量也是通过常量 PIPE_BUF 来指定的。
FIFO 有下面两种用途。
(1)shell 命令使用 FIFO 将数据从一条管道传送到另一条时,无需创建中间临时文件。
(2)客户-服务器进程应用程序中,FIFO 用作汇聚点,在客户进程和服务器进程之间传递数据。
下面的两个实例分别说明了这两种用途。
实例一:用 FIFO 复制输出流
FIFO 可用于复制一系列 shell 命令中的输出流,这就防止了将数据写向磁盘文件,类似于使用管道来避免中间磁盘文件。考虑这样一个过程,它需要对一个经过过滤的输入流进行两次处理。可以像下面这样使用 FIFO 和 tee(1) 程序来实现这样的过程而无需使用临时文件(tee 程序将其标准输入同时复制到其标准输出以及其命令行中命名的文件中)。
$ mkfifo fifo1 $ prog3 < fifo1 & $ prog1 < infile | tee fifo1 | prog2
这里先在后台启动 prog3 来从 FIFO 读数据,然后使用 tee 将 prog1 的输出发送到 FIFO 和 prog2。下图显示了这样的进程安排。
实例二:使用 FIFO 进行客户进程-服务器进程通信
FIFO 的另一个用途是在客户进程和服务器进程之间传送数据。假设有一个服务器进程的每个客户进程都可以将它们的请求写到一个该服务器进程创建的众所周知的 FIFO 中。为避免客户进程的多次写之间的交叉,各客户进程发送给服务器的请求的长度要小于 PIPE_BUF 字节。为了将响应发送给各客户进程,服务器进程不能使用单个 FIFO,因为客户进程不可能知道何时去读它们的相应以及何时响应其他客户进程。一种解决办法是,每个客户进程都在其请求中包含它的进程 ID,然后服务器进程基于每个客户进程的进程 ID 为各个客户创建一个 FIFO,例如服务器进程可以用路径 /tmp/serv1.xxxx 创建 FIFO,其中 xxxx 表示客户进程的进程 ID。下图显示了这种安排。
虽然这种安排可以工作,但服务器进程不能判断一个客户进程是否崩溃终止,这就使得客户进程专用的 FIFO 会遗留在文件系统中。另外,服务器进程还必须得捕捉 SIGPIPE 信号,以免客户在发送请求后还没读取响应就终止了。还有如果服务器进程以只读方式打开众所周知的 FIFO,则每当客户进程个数变为 0 时,服务器进程就将在 FIFO 中读到一个文件结束标志。为使服务器进程免于处理这种情况,一种常用的技巧是使服务器进程以读-写方式打开该 FIFO。
上一篇: SCTP 套接字选项
下一篇: Http和Socket详解