Linux 进程间通信之管道
1.什么是管道
管道可以用来在两个进程之间传递数据,其实我们已经用过管道了,比如 ps -ef | grep bash ,
| 就是个管道,其作用就是将 ps 命令的结果写入管道文件,然后 grep 再从管道文件中读出该数据进行过滤。
管道文件的读写是在内存上进行的。管道文件有一个读端还有一个写端,如果对一个管道文件只进行读而没有写,或者只进行写而没有读,就会产生阻塞的。就像两个人要打电话,两个人要同时打开手机进行通信,自己是没有办法通信的,所以对一个管道文件进行操作,就必须同时有两个进程。
2.有名管道
有名管道可以在任意两个进程之间通信
2.1有名管道的创建
1)命令创建
mkfifo
FIFO
2)系统调用创建
#include <sys/types.h>
#include <sys/stat.h>
/ /filename 是管道名 mode 是创建的文件访问权限
int mkfifo(const char *filename, mode_t mode);
示例:
2.2 创建一个进程 a 要将从键盘获取的数据循环传递给另一个进程 b
我们已经在当前文件夹下创建了一个名fifo的管道文件。
创建a.cC文件对fifo文件进行写操作。代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<assert.h>
#include<fcntl.h>
int main()
{
int fdw = open("fifo",O_WRONLY);
assert( fdw != -1 );
printf("fdw = %d\n",fdw);
printf("请输入:\n");
char buff[128] = {0};
fgets(buff,128,stdin);
write(fdw,buff,strlen(buff));
close(fdw);
exit(0);
}
创建a.cC文件对fifo文件进行读操作。代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<assert.h>
#include<fcntl.h>
int main()
{
int fdr = open("fifo",O_RDONLY);
assert( fdr != -1 );
printf("fdr = %d\n",fdr);
char buff[128] = {0};
int n = read(fdr,buff,127);
printf("buff = %s,n = %d\n",buff,n);
close(fdr);
分别编译a.c和b.c然后分别执行可执行文件a和可执行文件b
我们发现两个进程产生了阻塞。**因为对管道文件操作,同时需要两个进程。**所以我们新打开一个终端,同时执行可执行文件a和可执行文件b。
两个进程没有产生阻塞。接下来向管道中写入hello,观察。
写端已写入,同时读端已读到。(注意:当管道文件内容为空,读端阻塞,当管道文件内容写满,写端阻塞。)
改进以上a.c和b.c可以在写端一直输入,读端一直读管道中的内容,当输入end时结束。
改进后a.c代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<assert.h>
#include<fcntl.h>
int main()
{
int fdw = open("fifo",O_WRONLY);
assert( fdw != -1 );
printf("fdw = %d\n",fdw);
while(1)
{
printf("请输入:\n");
char buff[128] = {0};
fgets(buff,128,stdin);
if(strncmp(buff,"end",3) == 0)
{
break;
}
write(fdw,buff,strlen(buff));
}
close(fdw);
exit(0);
}
改进后b.c代码如下:
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<assert.h>
#include<fcntl.h>
int main()
{
int fdr = open("fifo",O_RDONLY);
assert( fdr != -1 );
printf("fdr = %d\n",fdr);
while(1)
{
char buff[128] = {0};
int n = read(fdr,buff,127);
if(n == 0)
{
break;
}
printf("buff = %s\n",buff);
}
close(fdr);
exit(0);
}
编译同时运行两个可执行程序a和b:
键盘输入end,写端结束进程,同时读端也结束,因为当写端进程结束,读端在管道中读到的内容为0,b.c中当读到内容为0时,退出循环。
那么当读端进程结束,写端进程会怎么样呢?我们实验一下!
我们发现当读端进程结束,写端进程也结束。这是因为:**当管道读端关闭,写端写入数据触发异常,内核就会给写端发送信号,使之进程结束。**就像两个人打电话,对方把电话挂了,通话也就结束了。
3.无名管道
无名管道只在父子进程间通信。
无名管道用pipe()创建,用man命令查看pipe方法。
返回值类型为int型,成功返回0,失败返回-1,参数为一个int型数组,数组大小为2,pipefd[0]是管道读端的描述符,pipefd[1]是管道写端的描述符。
代码演示:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<string.h>
int main()
{
int pipefd[2];
int res = pipe(pipefd);
assert(res != -1);
pid_t pid = fork();
assert(pid != -1);
if(pid == 0)
{
close(pipefd[1]);
char buff[128] = {0};
read(pipefd[0], buff,127);
printf("child buff = %s\n",buff);
close(pipefd[0]);
}
else
{
close(pipefd[0]);
char buff[128] = {0};
fgets(buff,128,stdin);
write(pipefd[1],buff,strlen(buff));
close(pipefd[1]);
}
exit(0);
}
编译运行:
4.管道的特点
无论有名还是无名,写入管道的数据都在内存中
管道是一种半双工的通信方式
有名管道和无名管道的区别:有名管道可以在任意进程间使用,无名管道只能在父子进程间通信