一、什么是管道
在Linux系统下,一切皆文件,所以管道就是一个文件,用来实现进程间通信的一种方式。分析小技巧:对于一些陌生的概念,都把它当成是文件,然后操作的时候,就是三部曲。文件打开,文件读写,文件关闭。
二、有哪几种管道
匿名管道和具名管道。有些地方又称为无名管道和有名管道。
三、管道的特性
1、匿名管道(PIPE)
(1) 匿名管道没有名称,因此无法使用open创建或者打开,事实上匿名管道有自己独特的创建接口。
(2)匿名管道只能用于父子进程之间的通信,不能用于别的进程。因为匿名管道没有名称,别的进程无法创建和打开,二而父子进程遵循读时共享,写时复制的原则,自然就能够拿到匿名管道的文件描述符,然后就可以通信。
(3)匿名管道描述符只能通过继承的方式传递给后代进程,只能用于亲缘进程间(通常用于父子进程间)的通信。
(4)匿名管道拥有两个文件描述符,一个专用于读fd[0],一个专用于写fd[1],因此在创建管道时,必须传递一个至少包含两个整型元素的数组。
(5)不能有多个进程同时对匿名管道进行写操作,否则数据有可能被覆盖。
匿名管道适用于一对一的、具有亲缘关系的进程间的通信。
2、具名管道(FIFO)
(1)具名管道通常被称为FIFO(First In First Out),存放的数据都是按顺序被读出的。
(2)具名管道更接近普通文件,有文件名,可以使用open()函数打开,支持read()/write()等读写操作。
(3)有专门的接口创建:mkfifo()。创建新的具名管道文件时,必须保证创建路径位于Linux系统内,尤其是虚拟机种操作的时候,不可将管道文件创建在共享文件夹中,因为共享文件夹是windows系统的,不支持管道文件。
(4)支持多路同时写入。
3、管道读写特性
管道文件默认是阻塞的,可以修改文件描述的阻塞特性来达到目的。
四、管道的接口API有哪些
// 匿名管道 #include <unistd.h> int pipe(int fd[2]); 返回值:成功返回0, 失败返回-1 // 具名管道 #include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode); 返回值:成功返回0, 失败返回-1
五、案例
1、匿名管道
#include <stdio.h> #include <sys/types.h> #include <string.h> #include <unistd.h> int main(int argc, char *argv[]) { int fd[2]; int ret = pipe(fd); // 创建匿名管道 if(ret == -1) { perror("pipe fail\n"); return -1; } char msg[128] = {0}; pid_t pid = fork(); // 创建子进程 if(pid > 0) // 父进程 { while(1) { printf("please input data:"); scanf("%s", msg); // 匿名管道fd[1]是写入端 write(fd[1], msg, strlen(msg)); bzero(msg, sizeof(msg)); } } else if(pid == 0) // 子进程 { int i = 1; int ret = 0; while(1) { bzero(msg, sizeof(msg)); while(1) { // 匿名管道fd[0]是接收端 ret = read(fd[0], msg, 128); if(i == 1) { printf("\nrecv data from parent process:\n"); i--; } printf("%s", msg); if(ret < 128) { i = 1; printf("\n"); break; } } } } else { perror("fork fail\n"); return -1; } return 0; }
2、具名管道
// 具名管道操作示例 #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #define FIFO_PATH "/tmp/fifo.txt" // 具名管道文件路径,不能在共享文件夹路径下创建 int main(int argc, char *argv[]) { int ret = access(FIFO_PATH, F_OK); // 判断管道文件是否存在 if(ret == -1) { ret = mkfifo(FIFO_PATH, 0777); // 不存在就创建具名管道 if(ret == -1) { perror("mkfifo fail\n"); return -1; } } int fd = open(FIFO_PATH, O_RDWR); // 打开管道文件 if(fd == -1) { perror("open fail\n"); return -1; } char msg[128] = {0}; pid_t pid = fork(); if(pid > 0) // 父进程 { while(1) { printf("please input data: "); scanf("%s", msg); write(fd, msg, strlen(msg)); // 像普通文件操作一样写入数据 bzero(msg, sizeof(msg)); } } else if(pid == 0) // 子进程 { int i = 1; int ret1 = 0; while(1) { while(1) { bzero(msg, 128); ret1 = read(fd, msg, 128); // 像普通文件一样读取数据 if(i == 1) { printf("recv data from parent process:"); i = 0; } printf("%s", msg); if(ret1 < 128) { i = 1; printf("\n"); break; } } } } else { perror("fork fail\n"); close(fd); // 关闭管道文件 return -1; } close(fd); // 关闭管道文件 return 0; }
3、设置非阻塞特性
// 管道非阻塞操作示例 #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #define FIFO_PATH "/tmp/fifo.txt" int main(int argc, char *argv[]) { int ret = access(FIFO_PATH, F_OK); if(ret == -1) { ret = mkfifo(FIFO_PATH, 0777); if(ret == -1) { perror("mkfifo fail\n"); return -1; } } int fd = open(FIFO_PATH, O_RDWR); if(fd == -1) { perror("open fail\n"); return -1; } // 设置非阻塞特性 int status = fcntl(fd, F_GETFL); // 获取文件特性 status |= O_NONBLOCK; // 加上非阻塞特性 fcntl(fd, F_SETFL, status); // 重新设置文件特性 char msg[128] = {0}; while(1) { printf("read data:\n"); ret = read(fd, msg, 128); // 非阻塞状态下会一直循环 if(ret <= 0 ) { printf("no data\n"); } else { printf("%s\n", msg); } sleep(1); // 加上睡眠可以减少程序对CPU的占用 } close(fd); return 0; }
六、总结
匿名管道用于通常用于父子进程间的通信,而具名管道用于任何多个进程间的通信,管道文件默认是阻塞的,管道的数据读取都是按顺序的,不能使用lseek()函数跳过某个字节。