1、一些说明
有名管道与无名管道的区别是文件系统里面有没有文件名。
在应用层中有“进程a”和“进程b”两个进程,这两个进程在应用层里面是不能直接交流的,它们必须通过内核才能进行通讯;利用管道,进程a往管道里面写数据,进程b从管道中去读数据,这样就实现了进程a和进程b之间的通讯。
2、无名管道
无名管道只能实现有亲缘关系间的进程通信,比如说父子进程。像上面提到的进程a与进程b,它们之间无亲缘关系,就不能使用无名管道了。
(1)pipe函数
作用:创建管道
头文件
#include<unistd.h>
函数原型
int pipe(int pipefd[2])
参数 pipefd[2]:这个参数是得到的是文件描述符,而不是传进去的文件描述符
在“管道”中,有两个对应的文件描述符,一个是读端,一个是写端。
读:fd[0] 写:fd[1]
(2)无名管道使用步骤:
a. 调用pipe()创建无名管道;
b.fork()创建子进程,一个进程读,使用 read(),一个进程写,使用 write()。
(3)无名管道测试示例
在编写无名管道测试代码时,存在一个疑问:在fork函数之前还是fork函数之后调用pipe函数来创建管道呢?还是说在这两个位置都可以嘞?
无名管道只能实现有亲缘关系间的进程的通信,因为它要保证对同一个管道进行操作。如何保证呢?在fork函数之前创建pipe函数,就可以保证是在同一个管道里了。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(void)
{
//定义buf变量。然后让子进程从管道中读数据,让父进程往管道中写数据
char buf[32] = {0};
pid_t pid;
// 定义一个变量来保存文件描述符 ,且数量为2个
// 因为有一个读端,一个写端,所以数量为 2 个
int fd[2];
//调用pipe函数来创建一个管道
pipe(fd);
//打印读端和写端的文件描述符
printf("fd[0] is %d\n", fd[0]);
printf("fd[2] is %d\n", fd[1]);
// 创建进程
pid = fork();
if (pid < 0)
{
printf("error\n");
}
if (pid > 0)
{
//让父进程往管道里写数据,调用write函数
//父进程只用到了写端,故一开始便关闭读端,写完之后再关闭写端
int status;
close(fd[0]);
write(fd[1], "hello", 5);
close(fd[1]);
//在父进程中调用wait函数,会令其阻塞在那里
wait(&status);
exit(0);
}
if (pid == 0)
{
//在子进程中调用read函数,读到的数据放入buf中,读32个
//在子进程中,只用到了读端,故暂时关闭写端
close(fd[1]);
read(fd[0], buf, 32);
printf("buf is %s\n", buf);
//读完之后,将读端也关闭
close(fd[0]);
exit(0);
}
return 0;
}
在该程序中,如果父进程不写入数据,如下代码所示:
if (pid > 0)
{
int status;
wait(&status);
exit(0);
}
则运行之后会发现子进程不会 printf(“buf is %s\n”, buf)打印该打印的东西,为啥呢?因为父进程不往管道里面写东西,父进程阻塞了(管道里面没有东西,阻塞了),需要在父进程中调用write函数,让父进程往管道里写数据。
把程序写完整之后,在ubuntu上的编译结果如下:
观察ubuntu界面的编译运行结果,可以看到文件描述符的返回值是3和4,为什么是3和4呢?因为文件描述符0,1,2代表的是标准输入、标准输出、标准出错,所以是从3开始的。
此外,有一点需要注意,这个管道是创建在内存里面的,假如进程已经结束了,那么这个空间就会得到释放,管道也就不存在了。管道里面的东西也是读完就没有啦,读完就删除了。
3、有名管道
有名管道可以实现没有任何关系的进程之间的通信。
因为有名管道是在文件系统里面存在这样一个文件名,它是一个特殊的文件类型,是管道类型。只需要知道这个文件所在的路径,就可以进行没有任何亲缘关系的进程间的通信。在使用有名管道时,第一步是先在文件系统里面创建这样一个文件名,创建这个文件名不能使用open函数或者creat函数来完成,因为open函数这一类函数只能创建普通文件,而不能创建特殊文件。那么这一类的特殊文件(管道类型文件)如何创建呢?这里使用mkfifo函数,该函数位于man手册的第三页。
(1)mkfifo函数
该函数的作用是:创建一个FIFO这样的特殊文件,如果创建成功的话,返回值为0;创建失败,返回值为-1.
头文件:
#include<sys/types.h>
#include<sys/stat.h>
函数:
int mkfifo(const char *pathname, const char *pathname)
其中,const char *pathname指的是“创建的FIFO文件的文件名”
const char *pathname指的是“权限”,该权限与umask有关
存在这样的函数,那么就存在这么一个命令,在ubuntu界面输入“man 1 mkfifo”,会出现“mkfifo”命令,该命令的功能是创建FIFO,使用方法是“mkfifo + FIFO文件名”
即:在文件系统中创建一个FIFO文件,有两种方法:一是直接使用命令创建;二是在代码中调用mkfifo函数。
(2)有名管道测试示例
-
a、使用命令创建fifo
使用ls -al命令查看其权限,可以看到fifo文件的类型为p,意为管道类型;可以看到它的大小为0,意思是该管道类型的文件只有inode号,并不占磁盘的空间(块设备文件也不占用磁盘空间) -
b、使用 mkfifo()函数创建有名管道的示例
第一步:读数据
从有名管道里面读数据,同样是从命令行里面传进去它要打开的文件,以“只读”的方式打开,打开之后,每隔一秒种,就将fifo文件里面的数据读出来,放到buf里面,然后打印buf的内容。
读数据的代码如下:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buf[32] = {0};
int fd;
if (argc < 2)
{
printf("Usage:%s <fifo name> \n", argv[0]);
return -1;
}
fd = open(argv[1], O_RDONLY);
while (1)
{
sleep(1);
read(fd, buf, 32);
printf("buf is %s\n", buf);
memset(buf, 0, sizeof(buf));
}
close(fd);
return 0;
}
在ubuntu界面编译该读文件的代码并运行,结果如下:
观察该“读数据”的代码的编译运行结果可以看到,阻塞
啦,并没有打印buf的内容,说明不论是有名管道还是无名管道都有阻塞的属性,当管道里面没有数据时就会阻塞在那里
接下来,重新打开一个终端,编译写入数据的代码,代码如下:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int ret;
char buf[32] = {0};
int fd;
if (argc < 2)
{
printf("Usage:%s <fifo name> \n", argv[0]);
return -1;
}
if (access(argv[1], F_OK) == 1)
{
ret = mkfifo(argv[1], 0666);
if (ret == -1)
{
printf("mkfifo is error \n");
return -2;
}
printf("mkfifo is ok \n");
}
fd = open(argv[1], O_WRONLY);
while (1)
{
sleep(1);
write(fd, "hello", 5);
}
//每隔一秒钟就打印hello
close(fd);
return 0;
}
编译fifo_write文件并执行,让它往fifo里面写数据,再执行fifo_read 看是否可以执行成功?
运行结果如下,可以看到可执行文件fifo_read每隔一秒钟就显示 buf is hello。
将fifo_write进程kill掉,发现fifo_read还在打印,只是内容为空。(如下图所示)此时在fifo_read所在的终端里将该进程kill掉就终止运行啦