因为进程之间的独立性,无法直接沟通,所以操作系统提供了公共的媒介,使多个进程之间可以共享同样的资源,这种机制就是进程间通信。不同的场景,就会有不同的方式,本篇博客会详解通信方式中的管道。
管道的其实就是内核中的一块缓存区,多个内存通过访问同一块缓冲区,实现进程间通信。
管道的种类有两种,一种是匿名管道,一种是命名管道。
匿名管道
-
本质:
就是一个内核缓冲区,进程通过内核缓冲区的访问实现进程间通信 -
特征:
<1> 半双工通信
进程A往文件内写入数据,那么进程B就只能读取文件中的数据,不能再进行写操作。
<2>管道的生命周期随进程
因为其他具有亲缘进程,是通过复制父进程的方式访问获取到同一块管道的操作句柄,进而访问同一块缓冲区,所以父进程结束后,这个管道也就会随父进程结束。
<3>管道自带同步与互斥
同步:通过条件的判断,实现对资源访问的合理性
互斥:同一时间只有一个执行休能够操作临界资源,保证数据操作的安全性。
同步特性:
①如果管道内没有数据,read会阻塞,管道中数据写满,write继续写入数据会阻塞,知道数据被读取出去。
②如果写端被关闭,那么read读完数据后不会阻塞,而是返回0
③如果读端被关闭,那么write写入数据,会触发异常,进程退出
互斥特性:
①若读写的数据不超过4096,那么这个操作不会被打断,保证了操作的原子性。
<4>管道提供字节流服务
数据在缓冲区堆积,便于取数据。
匿名管道的代码实现
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
int main()
{
int pipefd[2];
int pp = pipe(pipefd);//切记管道的创建一定是在,子进程创建之前
//pipefd[0]---用于读取数据
//pipefd[1]---用于写入数据
int pid = fork();
if(pid == 0)
{
char buf[1024] = {0};
pp = read(pipefd[0],buf,1023);
//此处我没有判断错误,让代码简洁易懂
printf("[%s]\n",buf);
}
else
{
char* str = "zcssgsssynhzj";
pp = write(pipefd[1],str,strlen(str));
}
return 0;
}
命名管道(FIFO)
- 本质:
提供了一个设备文件FIFO,进程通过访问路径在文件系统中找到这种歌文件,进而实现了非亲缘关系的进程间通信。 - 特性:
<1> 可用于同一主机之间的任意进程间通信
<2> 若命名管道以只读方式打开,则会阻塞到管道文件被以写的方式打开,同理只写会阻塞到被以只读方式打开。
<3> 命名管道具有上边匿名管道的特性,只不过命名管道的生命周期随系统。
命名管道的代码实现
//这个是read.c
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
char* str = "fifo";
int pp = mkfifo(fifo,0666);
int fd = open(str,O_RDONLY);
if(fd < 0)
{
peero("error");
return -1;
}
while(1)
{
char buf[1024] = {0};
int rd = read(fd,buf,1023);
if(rd == 0)
{
printf("stop\n");
}
else if(rd < 0)
{
perror("error");
return -1;
}
printf("[%s]\n",buf);
}
return 0;
}
这是fifo_write.c
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
char* str = "fifo";
int pp = mkfifo(fifo,0666);
if(pp < 0)
{
if(errno != EEXIST)
{
perror("error");
return -1;
}
}
int fd = open(str,O_WRONLY);
if(fd < 0)
{
peero("error");
return -1;
}
while(1)
{
char buf[1024] = {0};
int wr = write(fd,buf,strlen(buf));
printf("[%s]\n",buf);
if(wr == 0)
{
printf("stop\n");
}
else if(wr < 0)
{
perror("error");
return -1;
}
}
return 0;
}
Linux下同时运行写端和读端,在写端写入数据是,读端会读取数据,结果如下图
本质的区别显而易见以及在上边提到了,这里要说一下的是两个管道使用时候的区别:
匿名管道的生命周期随进程,它在读取数据的时候使用的是read(),一般代码实现在一个代码中,通过父子这两个进程的读写,来实现进程间通信。
命名管道的生命周期随系统,命名管道是一个设备文件,进程通过打开这个fifo文件,进行操作,调用时候使用的是open(),在读端一O_RDONLY方式打开,那么它会阻塞直到管道文件被其他进程以写的方式打开,在写端以O_WRONLY方式打开,同理会阻塞到被管道文件被其他进程以读的方式打开。