有名管道介绍及使用
匿名管道,由于没有名字,只能用于有亲缘关系的进程间通信(通过进程fork 拷贝内核区的文件描述符这个机制来实现),为了克服这个缺点提出了有名管道(FIFO 先入先出)。
有名管道(FIFO 也是由环形队列实现)不同于匿名管道之处在于它提供路径名与这个管道关联,以FIFO的文件形式存在于文件系统中,并且其打开方式与打开一个普通文件的方式是一样的,这样即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径就可以彼此通过FIFO互相通信,因此通过FIFO不相关的进程也能交换数据。
一旦打开了FIFO,就能在它上面使用与操作匿名管道和其他文件的系统调用一样的I/O系统调用(如read、write、close),与管道一样,FIFO也有一个写入端和读取端,并且从管道中读取数据的顺序与写入的顺序是一样的(先入先出)。
FIFO与pipe(匿名管道)的不同点:
1 FIFO在文件中作为一个特殊文件存在,但是FIFO中的内容却存放在内存中(内核中的一个缓存区中)
2 当使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便以后使用
3 FIFO有名字,不相关的进程可以通过打开有名管道进行通信
有名管道的使用
通过命令创建有名管道 mkfifo 管道名
通过函数创建有名管道
#include
#include
int mkfifo(const char pathname,mode_t mode);
一旦使用mkfifo创建了一个FIFO就可以用open,常见的文件IO操作含糊书(close read wirte unlink删除)都可以用于FIFO文件
FIFO严格遵顼先入先出,对管道及FIFO的读总是从开始处返回数据(单向队列队首数据),对它们的写则把数据添加到末尾(向单向队列队尾添加数据),FIFO文件不支持诸如lseek()等文件定位操作。
一个为只读(写)而打开(open)的管道的进程会阻塞(阻塞在open),直到另一个进程以包含写(读)的方式打开相同的管道,才不会继续阻塞而是继续向下运行。
1 当指向管道写端的fd都close了,此时有进程从管道读端读数据,若此时管道中有剩余数据则读取剩余数据,直到剩余数据被读完后,再次read返回0(读到文件末尾read是返回0的 读出现异常是返回-1的)。
2 有指向管道写端的文件描述符没有关闭(管道写端的引用计数大于0)并且没有进程向管道写端写数据,此时将管道内数据读空后再继续读是出现read阻塞,直到有进程写了一些数据,read读到返回读到的字节数。
3 所有的指向管道读端的文件描述符 都关闭了(管道读端的引用计数为0),此时有进程向管道中写数据,那么该进程会收到一个信号SIGPIPE,通常这个信号会导致进程异常终止。
4 有指向管道读端的文件描述符没有关闭(管道读端的引用计数大于0)并且没有进程从管道读端读数据,此时*有进程向管道中写数据,在管道写满后,再次调用写write会导致写进程阻塞,直到管道中有空位write才不会阻塞并返回写入的字节数。
//为了实验 还需要创建两个进程 我们通过两个.c文件来实现//这个.c为写fifo进程#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <stdlib.h>#include <unistd.h> //acess#include <fcntl.h> //open#include <string.h> //strlenint main(){//判断文件是否存在int ret = access("f1", F_OK); //判断叫这个名字的文件是否存在if (ret == 0) //return 0 表示管道以存在{;}else{int ret = mkfifo("f1", 0664);if (ret == -1){perror("mkfifo:");return -1;}}//打开管道// 这个进程只向管道里写数据 所以以只写的方式打开管道int fd = open("f1", O_WRONLY); //以只写的形式打开 若没有其他线程以包含读的方式打开文件 这个open会一直阻塞在这!!!!if (fd == -1){perror("open:");return -1;}//写数据for (int i = 0; i < 100; i++){char buf[1024];sprintf(buf, "hello %d\n", i); //向buf中写数据printf("write data:%s\n", buf); //打印当前buf中的字符数据write(fd, buf, stelen(buf)); //向管道中写数据sleep(1);}//当所有读端都关闭 此时有写线程向管道中写数据会收到SIGPIPE信号导致进程直接异常退出close(fd); //关闭管道return 0;}//为了实验 还需要创建两个进程 我们通过两个.c文件来实现//这个.c为读fifo进程#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <stdlib.h>#include <unistd.h> //acess#include <fcntl.h> //openint main(){//判断文件是否存在int ret = access("f1", F_OK); //判断叫这个名字的文件是否存在if (ret == 0) //return 0 表示管道以存在{;}else{int ret = mkfifo("f1", 0664);if (ret == -1){perror("mkfifo:");return -1;}}//打开管道// 这个进程只读管道里的数据 所以以只读的方式打开管道int fd = open("f1", O_RDONLY); //以只读的形式打开 若没有其他线程以包含写的方式打开文件 这个open会一直阻塞在这!!!!if (fd == -1){perror("open:");return -1;}//读数据while (1){char buf[1024] = {0};int len = read(fd, buf, sizeof(buf));//当写端关闭 并且管道中无数据 返回len=0if (len == 0)break;printf("recv buf %s\n", buf);memset(buf, 0, sizeof(buf));}close(fd); //关闭管道return 0;}
有名管道实现简单版聊天功能
A进程先给B发,B收到后再给A发,A收到后。。。就这么交替地进行
一个管道只能单向通信,要实现相互的收发需要用两个管道实现。
进程A 1 以只写方式打开fifo1 以只读的方式打开fifo2 2循环读写数据
while(1){ 获取键盘录入的数据不用scanf(遇到换行就结束)而使用fgets; 并将录入的数据写管道1; 读管道2;
}
进程B 1 以只读方式打开fifo1 以只写的方式打开fifo2 2循环读写数据
while(1){ 读管道1;
获取键盘录入的数据不用scanf(遇到换行就结束)而使用fgets; 并将录入的数据写管道2; }
要实现有名管道的同时读写 需要fork出子进程 父进程open fifo1的读端 进行读
子进程 open fifo2的写端 进行写
A
#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <stdlib.h>#include <unistd.h> //acess#include <fcntl.h> //open#include <string.h> //openint main(){//判断有名管道文件是否存在//存在则不再创建 不存在则创建int ret = access("fifo1", F_OK); //判断叫这个名字的文件是否存在if (ret == 0) //return 0 表示管道以存在{;}else //-1{int ret = mkfifo("f1", 0664);if (ret == -1){perror("mkfifo:");return -1;}}int ret = access("fifo2", F_OK); //判断叫这个名字的文件是否存在if (ret == 0) //return 0 表示管道以存在{;}else //-1{int ret = mkfifo("f1", 0664);if (ret == -1){perror("mkfifo:");return -1;}}// 以只写的方式打开管道fifo1int fdw = open("fifo1", O_WRONLY);if (fdw == -1){perror("open FIFO1 WRITE:");return -1;}//以只读的方式打开管道fifo2int fdr = open("fifo2", O_RDONLY);if (fdr == -1){perror("open FIFO2 READ:");return -1;}char buf[128];//循环写读数据while (1){//写数据 写fifo1memset(buf, 0, sizeof(buf));fgets(buf, sizeof(buf), stdin); //从屏幕的stdin标准输入(标准输入也有缓冲区) 获取128个字节并装到buf里//将从标准输入得到的数据写到fifo里int ret = write(fdw, buf, strlen(buf));if (ret == -1){perror("write:");return -1;}//读数据 读fifo2memset(buf, 0, sizeof(buf));int len = read(fdr, buf, strlen(buf));if (len <= 0){//读出错 -1 读到末尾(或fifo2的写端已经关闭了 并且管道内容被读完了)返回0perror("read:");return -1;}printf("buf:%s\n", buf);}close(fdw);close(fdr);return 0;}
B
#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <stdlib.h>#include <unistd.h> //acess#include <fcntl.h> //open#include <string.h> //openint main(){//判断有名管道文件是否存在//存在则不再创建 不存在则创建int ret = access("fifo1", F_OK); //判断叫这个名字的文件是否存在if (ret == 0) //return 0 表示管道以存在{;}else //-1{int ret = mkfifo("f1", 0664);if (ret == -1){perror("mkfifo:");return -1;}}int ret = access("fifo2", F_OK); //判断叫这个名字的文件是否存在if (ret == 0) //return 0 表示管道以存在{;}else //-1{int ret = mkfifo("f1", 0664);if (ret == -1){perror("mkfifo:");return -1;}}// 以只读的方式打开管道fifo1int fdr = open("fifo1", O_RDONLY);if (fdr == -1){perror("open FIFO1 READ:");return -1;}//以只写的方式打开管道fifo2int fdw = open("fifo1", O_WRONLY);if (fdw == -1){perror("open FIFO2 WRITE:");return -1;}char buf[128];//循环读写数据while (1){//读数据 读fifo1memset(buf, 0, sizeof(buf));int len = read(fdr, buf, strlen(buf));if (len <= 0){//读出错 -1 读到末尾(或fifo2的写端已经关闭了 并且管道内容被读完了)返回0perror("read:");return -1;}printf("buf:%s\n", buf);//写数据 写fifo2memset(buf, 0, sizeof(buf));fgets(buf, sizeof(buf), stdin); //从屏幕的stdin标准输入(标准输入也有缓冲区) 获取128个字节并装到buf里//将从标准输入得到的数据写到fifo里int ret = write(fdw, buf, strlen(buf));if (ret == -1){perror("write:");return -1;}}close(fdw);close(fdr);return 0;}
