进程间通信的机制包括:管道、信号量、共享内存、消息队列。
这篇博客主要介绍的是进程间通讯之管道的应用
一、管道的分类
管道都属于半双工通讯机制
管道分为有名管道和无名管道
1、有名管道
在磁盘上有一个管道文件标识,但是这个管道文件只会占据一个inode点,但是这个管道文件任何时候都不会占据block块。数据在传递过程中会缓存到内存上。这就是有名管道。
管道文件仅仅是为了使得不同的进程(有权限操作)能够共享
2、无名管道
没有管道文件,借助父子进程共享fork之前打开的文件描述符,来实现进程间通信。
二、创建有名管道文件
1、创建有名管道文件的方法
(1)命令:
mkfifo filename
(2)库函数:
int mkfifo()
创建有名管道用到的库函数的方法:open、read、write、close
2、通过有名管道实现进程间通讯
(1)我们在系统上创建两个文件,一个是mainA.c一个是mainB.c。mainA.c用来写文件,mainB.c用来读文件。
//mainA.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main()
{
int fd = open("./FIFO",O_WRONLY);
assert(fd != -1);
printf("Write open fifo success\n");
while(1)
{
char buff[128] = {0};
printf("input: ");
fgets(buff,127,stdin);
if(strncmp(buff,"end", 3)==0)
{
break;
}
write(fd,buff, strlen(buff) -1);
}
close(fd);
}
//mainB.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int fd = open("./FIFO",O_RDONLY);
assert(fd != -1);
printf("Read open fifo success\n");
while(1)
{
char buff[128] = {0};
int n = read(fd,buff,127);
if(n <= 0)
{
break;
}
printf("Read: %s\n",buff);
}
close(fd);
}
(2)如果我们只在系统上执行mainA.c我们发现什么也没有显示:
这块发生了阻塞,通过分析代码:
Write open fifo success
我们发现执行后这句没有执行,证明是在前面的open处阻塞了,这是因为,open操作的管道文件是以只写的方法打开的,没有任何读的方式去读它,它就会发生阻塞
(3)同样如果我们只在系统上执行mainB.c的话,也是同样的道理,发生阻塞
(4)当我们同时进行mainA.c和mainB.c的操作时,会发现,当我们在mainA.c中写一个文件mainB.c中就会读入一个。
(5)原理
原理就是通过两个进程在磁盘空间是共享的去访问磁盘上的FIFO结点,通过FIFO结点最终会指向内存上的一块空间,然后去写入数据和读取数据。
(6)注意事项
- open以一种方式打开管道文件会阻塞,直到有进程以另一种方式打开次管道文件
- 如果管道对应的内存空间中没有数据,则read会阻塞直到内存中有数据或者所有写端关闭才会退出。
- 如果管道对应的内存空间已满,则write就会阻塞,直到内存中有空间或者所有读端关闭才会返回。
三、创建无名管道文件
1、无名管道的创建与打开:
int pipe(int fds[2]);
创建无名管道用到的系统调用的方法:read、write、close
这块没有open是因为fds[2]中就已经包含了open的参数
2、通过无名管道实现进程间通讯
(1)我们只需创建一个文件pipe.c来实现
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
int main()
{
int fds[2];
int res = pipe(fds); //fds[0]:管道读端 fds[1]:管道写端
assert(res != -1);
pid_t pid = fork();
assert(pid != -1);
if(pid == 0)
{
close(fds[1]); //子进程直接关闭管道的写端
while(1)
{
char buff[128] = {0};
int n = read(fds[0],buff,127);
if(n <= 0)
{
break;
}
printf("Child: %s\n",buff);
}
close(fds[0]);
}
else
{
close(fds[0]); //父进程直接关闭管道的读端
while(1)
{
char buff[128] = {0};
printf("input: ");
fgets(buff, 127, stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
write(fds[1],buff,strlen(buff) -1);
}
}
close(fds[1]);
}
(2)我们发现子进程打印的东西在input之后。
这是因为:父进程将数据写入之后直接输出printf("input: ");
这个操作,不会等待子进程,只要write不阻塞,就会只执行自己的内容,子进程也是如此,父子进程执行相互之间没有关系。
(3)原理
四、管道的特点
- 无论有名还是无名, 写入管道的数据都在内存中
- 管道是一种半双工通信方式(通信方式有单工、半双工。全双工)
- 有名和无名管道的区别:有名可以在任意进程间使用,而无名主要在父子进程间。