1.进程间通信介绍
(1) 进程间通信(IPC):多个进程间进行信息交流
(2) 进程间通信的方式:管道、消息队列、共享内存、信号量、socket等;
(3) 进程间通信的目的:
数据传输:一个进程将他的数据发给另一个进程
资源共享:多个进程间共享同样的资源
通知事件:一个进程给另一个进程发消息,通知他发生了什么事件
进程控制:当一个进程想要完全控制另一个进程时,控制进程能拦截另一个进程的所有陷入和异常,并及时知道他的状态的改变
2.管道的引入
2.1 相关概念
(1)管道是最古老的进程间通信方式
(2)将一个进程连接到另一个进程的数据流称为“管道”
如who | wc -l的实现过程如下图:
who进程将要传输的数据输出到管道当中,wc -l进程从管道当中拿数据
2.2 管道的特点
(1)单向通信:管道是半双工通信,数据只能从管道的一端写入,从管道的另一端读出。可用两个管道实现全双工
(2)匿名管道只有具有亲缘关系(如父子进程)的进程间才可以通信,而命名管道在不相关的进程间也可以通信
(3)管道依赖文件系统:管道的生命周期随进程,进程结束则管道也随之销毁,因为管道的本体是内存,进程退出则这段内存销毁
(4)管道是基于字节流传输的:管道的读写没有规定大小,所以基于字节流传输
(5)管道自带同步与互斥:在管道的一端写入数据,若没有写完则另一端不能读,进入阻塞状态;同理,在一端读数据时若没有读完,则另一端不能写
2.3 管道的分类
管道分为匿名管道和命名管道
3.匿名管道
3.1管道的创建
头文件:#include<unistd.h>
功能:创建一个无名管道
原型:int pipe(int fd[2]);
参数:
文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:0表示创建成功,非0表示创建失败,失败返回错误码
3.2 创建父子进程,子进程向管道里写数据,父进程从管道里读数据
思考:父进程的fd[0]从管道中读数据,fd[1]向管道中写数据;创建子进程后,子进程也有fd[0]从管道中读数据,fd[1]向管道中写数据 。要实现子进程向管道里写数据,父进程从管道里读数据 ,就必须关闭子进程的读端、关闭父进程的写端。
代码实现:
#include<stdio.h>
#include<stdlib.h>
#include<error.h>
#include<unistd.h>
int main()
{
int fds[2];//文件描述符数组
if(pipe(fds)==-1)
{
perror("pipe");
exit(0);
}
pid_t id=fork();
if(id==0){//子进程
close(fds[0]);//关闭读
write(fds[1],"hello",5);//向管道写数据
close(fds[1]);
exit(0);
}
else if(id>0){//父进程
close(fds[1]);//关闭写
char buf[1024]={};
int r=read(fds[0],buf,1024);//从管道中读数据
if(r==-1)
{
perror("read");
close(fds[0]);
exit(0);
}else if(r==0)
{
printf("读结束\n");
close(fds[0]);
exit(0);
}else
{
printf("buf:[%s]\n",buf);
close(fds[0]);
exit(0);
}
}
else{
perror("fork");
exit(0);
}
return 0;
}
4.命名管道
4.1命名管道创建方式
(1) 命令创建:mkfifo 文件名
写端打开,若读端没有打开,则写端阻塞,直到读端打开才阻塞结束。即通过管道通信的读端写端同时打开。
(2)程序创建:
int mkfilo(const char* filename(文件名),mode_t mode(权限));
(3)打开管道文件:
int fd=open(name,O_TDONLY);//读
int fd=open(name,O_TDONLY);//写
4.2 创建一个匿名管道,从a-z,一次写一个字符,一次读一个字符
写端:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<error.h>
int main()
{
int fds[2];
mkfifo("namepipe",0644);//创建命名管道
int fp=open("namepipe",O_WRONLY);//打开命名管道
if(fp==-1){
perror("open");
exit(1);
}
char buf[2]={'a'};//缓存区,第0个字符为a
int i=0;
while(1){//循环写a-z
i++;
int r=write(fp,buf,1);
printf("write:r=%d [%c]\n",r,buf[0]);
buf[0]='a'+i%26;//26个字母,下标25
}
close(fp);
return 0;
}
读端:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<error.h>
int main()
{
int fds[2];
int fp=open("namepipe",O_RDONLY);
if(fp==-1){
perror("open");
exit(1);
}
char buf[2]={};
while(1){
read(fp,buf,1);
printf("read:r=[%c]\n",buf[0]);
sleep(1);
}
close(fp);
return 0;
}
结果:写端每次一个字符一个字符的不停的写数据,读端每次一个字符一个字符的读数据。
写端比读端快,当数据在命名管道中放满了之后写端阻塞,等读端将数据读走后管道有空间了再继续写。
5.命名管道与匿名管道的区别与联系
(1)匿名管道必须是具有亲缘关系的进程间才可以实现通信,命名管道对于不相关进程也可以实现通信
(2)匿名管道用pipe函数创建,创建的同时也打开;命名管道用mkpipe函数创建,打开用open。
(3)命名管道与匿名管道创建打开的工作完成后,他们具有相同的语义