进程间通信方式—管道
管道的特点:
- 管道只允许具有血缘关系的进程间通信,比如父子进程间通信(后来的命名管道可以实现非血缘关系的进程间通信);
- 管道只允许单向通信;
- 管道内部保证同步机制,从而保证访问数据的一致性;
- 面向字节流;
- 管道的生命周期随进程,进程关闭,对应的管道端口关闭,两个进程都关闭,则管道关闭。
1.1 匿名管道
管道调用是调用pipe()函数来创建:
#include <unistd.h>
int pipe (int fd[2]); //返回:成功返回0,出错返回-1
fd参数用来存放创建管道返回的两个文件描述符,fd[0]指向管道的读端,fd[1]指向管道的写端。
管道实现进程间通信:
- 父进程创建管道,得到两个文件描述符指向管道的两端;
- 父进程fork出子进程,子进程也有两个文件描述符,指向同一管道;
- 父进程关闭fd[0],子进程关闭fd[1],即父进程关闭管道读端,子进程关闭管道写端(因为管道只支持单向通信)。管道是用环形队列实现的,数据从写端写入,从读端读出,从而实现了进程间通信。
实例:父进程读,子进程写
#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{
int _pipe[2];
int ret=pipe(_pipe);
if(ret<0)
{
perror("pipe\n");
}
pid_t id=fork();
if(id<0)
{
perror("fork\n");
}
else if(id==0) // child
{
close(_pipe[0]);
int i=0;
char *mesg=NULL;
while(i<100)
{
mesg="I am child";
write(_pipe[1],mesg,strlen(mesg)+1);
sleep(1);
++i;
}
}
else //father
{
close(_pipe[1]);
int j=0;
char _mesg[100];
while(j<100)
{
memset(_mesg,'\0',sizeof(_mesg ));
read(_pipe[0],_mesg,sizeof(_mesg));
printf("%s\n",_mesg);
j++;
}
}
return 0;
}
结果演示:
总结:
- 如果一个管道的写端一直在写,而读端的引用计数是否大于0,决定管道是否会堵塞;引用计数大于0,只写不读再次调用write会导致管道堵塞;
- 如果一个管道的读端一直在读,而写端的引用计数是否大于0决定管道是否会堵塞;引用计数大于0,只读不写再次调用read会导致管道堵塞;
- 当他们的引用计数等于0时,只写不读会导致写端的进程收到一个SIGPIPE信号,导致进程终止;只读不写会导致read返回0,就像读到文件末尾一样。
1.2 命名管道FIFO
它提供一个路径名与之挂链,以FIFO的文件形式存储与文件系统中。命名管道是一个设备文件,因此,即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。值得注意的是, FIFO(first input first output)总是按照先进先出的原则工作,第一个被写入的数据将首先从管道中读出。
命名管道的创建:
创建命名管道的系统函数有两个: mknod和mkfifo。两个函数均定义在头文件sys/stat.h,这两个函数调用成功都返回0,失败都返回-1。
函数原型如下:
#include <sys/types.h>
#include <sys/stat.h>
//path为创建的命名管道的全路径名;
//mode为创建的命名管道的模,指明其存取权限;
//dev为设备值,该值取决于文件创建的种类,它只在创建设备文件时才会用到。
int mknod(const char *path,mode_t mode,dev_t dev);
//
int mkfifo(const char *path,mode_t mode);
umask(0);
if (mknod("/tmp/fifo",S_IFIFO | 0666) == -1)
{
perror("mkfifo error");
exit(1);
}
if (mkfifo("/tmp/fifo",S_IFIFO|0666) == -1)
{
perror("mkfifo error!");
exit(1);
}
"S_IFIFO|0666"指明创建一个命名管道且存取权限为0666,即创建者、与创建者同组的用户、其他用户对该命名管道的访问权限都是可读可写( 这里要注意umask对生成的管道文件权限的影响) 。命名管道创建后就可以使用了,命名管道和管道的使用方法法基本是相同的。只是使用命名管道时,必须先调用open()将其打开。因为命名管道是一个存在于硬盘上的文件,而管道是存在于内存中的特殊文件。
需要注意的是,调用open()打开命名管道的进程可能会被阻塞。但如果同时用读写方式( O_RDWR)打开,则一定不会导致阻塞;如果以只读方式( O_RDONLY)打开,则调用open()函数的进程将会被阻塞直到有写方打开管道;同样以写方式( O_WRONLY)打开也会阻塞直到有读方式打开管道。
命名管道和匿名管道的区别
- 匿名管道只能用于有血缘关系的进程间通信;而命名管道可以在不相关的进程之间和不同计算机之间使用。
- 命名管道可以实现单向或双向通信,而匿名管道只能单向通信。