2.3.3 有名管道
有名管道概述与原理
有名管道
(FIFO
,命名管道
)以一个文件实体的形式存在于磁盘
中,但有名管道
的大小始终为0 Bytes
。FIFO文件
被打开,将在内存
中生成管道
缓冲区,所有管道流经过该缓冲区,但始终不经过磁盘
。磁盘中的FIFO文件
仅用于定位有名管道
,不存储任何数据。- 与
管道
不同的,有名管道
可用于实现无公共祖先
的进程间通信。 - 与
管道
具有类似的结构和原理。
有名管道阻塞特征
有名管道
阻塞特征基本与管道
阻塞特征相同,需要注意的是:读端
必须先于写端
打开,详见下表。
管道阻塞特征 : { 操作读端 : { s i z e r e f n u m 打开读端 有进程引用写端 无进程引用写端 管道非空 阻塞,等待写端准备连接 正常读取,返回实际读取量 正常读取,返回实际读取量 管道为空 阻塞,等待写端准备连接 阻塞,等待管道写入 E O F ,返回 0 操作写端 : { s i z e r e f n u m 打开写端 有进程引用读端 引用读端文件描述符全部关闭 管道未满 阻塞,等待读端连接 正常写入,返回实际写入量 触发 S I G P I P E 信号,异常退出 管道已满 阻塞,等待读端连接 阻塞,等待管道读出 触发 S I G P I P E 信号,异常退出 管道非阻塞特征 : { 操作读端 : { s i z e r e f n u m 打开读端 有进程引用写端 无进程引用写端 管道非空 直接打开 正常读取,返回实际读取量 正常读取,返回实际读取量 管道为空 直接打开 返回 0 ,不更改接收变量 E O F ,返回 0 操作写端 : { s i z e r e f n u m 打开写端 有进程引用读端 引用读端文件描述符全部关闭 管道未满 返回-1,不存在设备 正常写入,返回实际写入量 触发 S I G P I P E 信号,异常退出 管道已满 返回-1,不存在设备 返回-1,管道已满 触发 S I G P I P E 信号,异常退出 \begin{array}{rl} 管道阻塞特征: & \left\{ \begin{array}{l} 操作读端:\left\{~~~~ \begin{array}{c|cc} {_{size}}{^{refnum}} & 打开读端 & 有进程引用写端 & 无进程引用写端 \\ \hline 管道非空 & 阻塞,等待写端准备连接 & 正常读取,返回实际读取量 & 正常读取,返回实际读取量 \\ 管道为空 & 阻塞,等待写端准备连接 & 阻塞,等待管道写入 & EOF,返回0\\ \end{array} \right. \\ \\ 操作写端:\left\{~~~~ \begin{array}{c|cc} {_{size}}{^{refnum}} & 打开写端 & 有进程引用读端 & 引用读端文件描述符全部关闭 \\ \hline 管道未满 & 阻塞,等待读端连接 & 正常写入,返回实际写入量 & 触发SIGPIPE信号,异常退出 \\ 管道已满 & 阻塞,等待读端连接 & 阻塞,等待管道读出 & 触发SIGPIPE信号,异常退出 \\ \end{array} \right. \end{array} \right. \\ \\ 管道非阻塞特征: & \left\{ \begin{array}{l} 操作读端:\left\{~~~~ \begin{array}{c|cc} {_{size}}{^{refnum}} & 打开读端 & 有进程引用写端 & 无进程引用写端 \\ \hline 管道非空 & 直接打开 & 正常读取,返回实际读取量 & 正常读取,返回实际读取量 \\ 管道为空 & 直接打开 & 返回0,不更改接收变量 & EOF,返回0\\ \end{array} \right. \\ \\ 操作写端:\left\{~~~~ \begin{array}{c|cc} {_{size}}{^{refnum}} & 打开写端 & 有进程引用读端 & 引用读端文件描述符全部关闭 \\ \hline 管道未满 & 返回\text{-1},不存在设备 & 正常写入,返回实际写入量 & 触发SIGPIPE信号,异常退出 \\ 管道已满 & 返回\text{-1},不存在设备 & 返回\text{-1},管道已满 & 触发SIGPIPE信号,异常退出 \\ \end{array} \right. \end{array} \right. \\ \\ \end{array} 管道阻塞特征:管道非阻塞特征:⎩ ⎨ ⎧操作读端:⎩ ⎨ ⎧ sizerefnum管道非空管道为空打开读端阻塞,等待写端准备连接阻塞,等待写端准备连接有进程引用写端正常读取,返回实际读取量阻塞,等待管道写入无进程引用写端正常读取,返回实际读取量EOF,返回0操作写端:⎩ ⎨ ⎧ sizerefnum管道未满管道已满打开写端阻塞,等待读端连接阻塞,等待读端连接有进程引用读端正常写入,返回实际写入量阻塞,等待管道读出引用读端文件描述符全部关闭触发SIGPIPE信号,异常退出触发SIGPIPE信号,异常退出⎩ ⎨ ⎧操作读端:⎩ ⎨ ⎧ sizerefnum管道非空管道为空打开读端直接打开直接打开有进程引用写端正常读取,返回实际读取量返回0,不更改接收变量无进程引用写端正常读取,返回实际读取量EOF,返回0操作写端:⎩ ⎨ ⎧ sizerefnum管道未满管道已满打开写端返回-1,不存在设备返回-1,不存在设备有进程引用读端正常写入,返回实际写入量返回-1,管道已满引用读端文件描述符全部关闭触发SIGPIPE信号,异常退出触发SIGPIPE信号,异常退出
有名管道操作
- 命令行生成管道文件
$ mkfifo <pathname> // 生成管道文件
- 创建、打开与读写管道文件
#include <sys/types.h>
#include <sys/stat.h>
// create a FIFO special file
// pathname:
// FIFO file path to create
// mode:
// create mode, same to mode in "open"
// return value:
// return 0 for success, or -1 for errors including file exist and etc.
int mkfifo(const char *pathname, mode_t mode);
// $ man 3 mkfifo
#include <fcntl.h>
// open a FIFO file in read or write mode
// pathname:
// FIFO file to read or write
// flags:
// O_RDONLY for FIFO read mode,
// O_WRONLY for FIFO write mode,
// O_NONBLOCK (optional) for non-block IO
// return value:
// return file descriptor of FIFO, or -1 for error
int open(const char *pathname, int flags);
// read
ssize_t read(int fd, void *buf, size_t count);
// write
ssize_t write(int fd, const void *buf, size_t count);
实际案例:本机聊天功能
- 为简化案例,本聊天功能仅实现 一对一 一来一回式 交互。
- 本聊天功能使用一体式程序,使用两方分离式程序更方便些。
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 错误处理函数: 打印错误信息并退出
void err(int ret, const char cmd[]) {
if (ret == -1) {
perror(cmd);
exit(0);
}
}
// 父进程函数: 处理子进程正常退出与异常退出问题
void parent() {
int wstatus;
int ret = wait(&wstatus);
if (access("chatr.fifo", F_OK) == 0) {
// 删除临时管道文件
unlink("chatr.fifo");
}
if (access("chatw.fifo", F_OK) == 0) {
// 删除临时管道文件
unlink("chatw.fifo");
}
if (WIFSIGNALED(wstatus)) {
printf("对方异常退出。\n");
}
}
// 子进程函数 - 主函数
void child() {
int flag = 0; // 对话发起方标记
if (access("chatr.fifo", F_OK) == -1) {
mkfifo("chatr.fifo", 0664);
flag = 1; // 首先创建管道文件的为对话发起方
}
if (access("chatw.fifo", F_OK) == -1) {
mkfifo("chatw.fifo", 0664);
flag = 1; // 首先创建管道文件的为对话发起方
}
int fdr, fdw;
if (flag) {
// 如果是对话发起方
printf("正在等待加入聊天...\n");
/* 注意:
chatr.fifo 与 chatw.fifo 文件在主客进程中打开次序必须保持一致,
否则由于阻塞机制存在, 进程将一直阻塞.
*/
fdr = open("chatr.fifo", O_RDONLY);
err(fdr, "open");
fdw = open("chatw.fifo", O_WRONLY);
err(fdw, "open");
printf("你是聊天的发起人。\n");
} else {
// 如果是对话接受方
printf("正在加入聊天...\n");
fdw = open("chatr.fifo", O_WRONLY);
err(fdw, "open");
fdr = open("chatw.fifo", O_RDONLY);
err(fdr, "open");
printf("你是聊天的接受人。\n");
}
char buf[1024];
int turn = flag;
while (1) {
if (turn) {
printf("到你发言了!\n> ");
gets(buf);
if (strcmp(buf, "/exit") == 0) {
int ret = write(fdw, buf, sizeof(buf));
err(ret, "write");
printf("你终止了对话。\n");
exit(0);
} else {
int ret = write(fdw, buf, sizeof(buf));
err(ret, "write");
printf("你: %s\n", buf);
}
} else {
printf("等待对方回应...\n");
int ret = read(fdr, buf, sizeof(buf));
err(ret, "read");
buf[ret] = '\0';
if (strcmp(buf, "/exit") == 0) {
printf("对方终止对话。\n");
exit(0);
} else {
printf("他: %s\n", buf);
}
}
turn = !turn;
}
}
int main() {
int pid = fork();
err(pid, "fork");
if (pid) {
parent();
} else {
child();
}
return 0;
}
效果呈现: