四十八、进程间通信——标准库中的管道操作及命名管道和匿名管道
48.1 标准库中的管道操作
48.1.1 标准库中的管道操作
1 #include <stdio.h> 2 FILE *popen(const char *cmdstring, congst char *type); 3 int pclose(FILE *fp)
- 函数说明:
- 使用 popen() 创建的管道必须使用 pclose() 关闭。其实,popen/pclose 和标准文件输入/输出流中的 fopen()/fclose 十分相似。
- 封装管道的常用操作
- 返回值:
- popen:成功,返回文件指针;出错,返回 NULL
- pclose:cmdstring 的终止状态,出错返回 -1
48.1.2 例子
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 6 int main(void) 7 { 8 FILE *fp; 9 /** 命令执行的结果放置在 fp 指向的结构体缓存中 */ 10 fp = popen("cat /etc/passwd", "r"); 11 char buf[512]; 12 memset(buf, 0, sizeof(buf)); 13 while(fgets(buf, sizeof(buf), fp) != NULL){ 14 printf("%s", buf); 15 } 16 17 pclose(fp); 18 19 printf("-----------------------------------------------------------------\n"); 20 /** 为 wc 命令提供统计的数据 */ 21 fp = popen("wc -l", "w"); 22 /** 向 fp 指向的结构体缓存中写入数据 */ 23 fprintf(fp, "1\n2\n3\n"); 24 pclose(fp); 25 26 return 0; 27 }
编译运行:
48.2 命名管道和匿名管道
48.2.1 命名管道的创建
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 int mkfifo(const char *pathname, mode_t mode);
- 只要对 FIFO 有适当访问权限,FIFO 可用在任何两个没有任何关系的进程间通信。
- 本质是内核中的一块缓存,另在文件系统中以一个特殊的设备文件(管道文件)存在
- 在文件系统中只有一个索引块存放文件的路径,没有数据块,所有数据块存放在内核中
- 命名管道必须读和写同时打开,否则单独读或者单独写会引发阻塞
- 命令 mkfifo 创建命名管道(命令内部调用 mkfifo 函数)
- 对 FIFO 的操作与普通文件一样
- 一旦已经用 mkfifo 创建了一个 FIFO,就可用 open 打开它,一般的文件 I/O (close、read、write、unlink 等)都可用于 FIFO
- FIFO 相关出错信息
- EACCES:无存取权限
- EEXIST:指定文件不存在
- ENAMETOOLONG:路径不存在
- ENOENT:包含的目录不存在
- ENOSPC:文件系统剩余空间不足
- ENOTDIR:文件路径无效
- EROFS:指定的文件存在于只读文件系统中
48.2.2 命名管道例子
创建两个没有关系的程序,即两个进程,通过命名管道进行读写
fifo_read.c
1 #include <unistd.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <fcntl.h> 5 #include <memory.h> 6 7 /** 从命名管道中读取数据 */ 8 int main(int argc, char *argv[]) 9 { 10 if(argc < 2){ 11 printf("usage: %s fifo\n", argv[0]); 12 exit(1); 13 } 14 printf("open fifo read ...\n"); 15 /** 打开命名管道 */ 16 int fd = open(argv[1], O_RDONLY); 17 if(fd < 0){ 18 perror("open error"); 19 } 20 else { 21 printf("open file success: %d\n", fd); 22 } 23 24 /** 从命名管道中读取数据 */ 25 char buf[512]; 26 memset(buf, 0, sizeof(buf)); 27 while(read(fd, buf, sizeof(buf)) < 0){ 28 perror("read error"); 29 } 30 printf("%s\n", buf); 31 32 close(fd); 33 return 0; 34 }
fifo_write.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <memory.h> 5 #include <fcntl.h> 6 7 8 int main(int argc, char *argv[]) 9 { 10 if(argc < 2){ 11 printf("usage: %s fifo \n", argv[0]); 12 exit(1); 13 } 14 printf("open fifo write...\n"); 15 16 /** 打开命名管道 */ 17 int fd = open(argv[1], O_WRONLY); 18 if(fd < 0){ 19 perror("open error"); 20 exit(1); 21 } 22 else { 23 printf("open fifo success: %d\n", fd); 24 } 25 26 char *s = "hello world"; 27 size_t size = strlen(s); 28 if(write(fd, s, size) != size){ 29 perror("write error"); 30 } 31 close(fd); 32 33 return 0; 34 }
单独编译这两个文件为可执行程序,先在一个终端中运行read,再在另一个终端运行 write。
在没有打开 write 之前,read 会阻塞,在打开之后,read 即可正常读取
48.3 匿名管道和命名管道读写的差异
- 相同点
- 默认都是阻塞性读写
- 都适用于 socket 的网络通信
- 阻塞不完整管道(有一端关闭)
- 单独读时,在所有数据被读取后,read 返回 0,以表示到达了文件尾部
- 单纯写时,则产生信号 SIGPIPE,如果忽略该信号或捕捉该信号并从处理程序返回,则 write 返回 -1,同时 errno 设置为 EPIPE
- 阻塞完成管道(两端都开启)
- 单纯读时,要么阻塞,要么读取到数据
- 单纯写时,写到管道满时会出错
- 非阻塞不完整管道(有一端关闭)
- 单纯读时直接报错
- 单纯写时,则产生信号 SIGPIPE,如果忽略该信号或捕捉该信号并从处理程序返回,则 write 返回 -1,同时 errno 设置为 EPIPE
- 非阻塞完整管道(两端都开启)
- 单纯读时直接报错
- 单纯写时,写到管道满时会出错
- 不同点:
- 打开方式不一样
- pipe(匿名管道) 通过 fcntl 系统调用来设置 O_NONBLOCK 来设置非阻塞性读写
- FIFO 通过 fcntl 系统调用或者 open 函数来设置非阻塞性读写