什么是进程间通信?
字面意思就是让俩个进程之间可以互相传递信息,本质就是让俩个毫不相干的进程看到同一份资源。
进程间通信的目的:
数据传输:一个进程需要将它的数据发送给另一个进程。
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异
常。并能够及时知道它的状态改变。
而进程间通信的方式也有几种:
管道:分为匿名管道pipe和命名管道。
System V IPC:可分为System V 消息队列,System V 共享内存,System V 信号量这三种。
什么是管道?
今天我们主要来解析管道。说起管道,我们生活中也有很多例子,比如自来水管,油管等都是通过管道 传递一些东西的实例。
而对于进程之间的管道是什么样呢?管道是一个文件,称为管道文件。管道是Unix中最古老的进程间通信的形式。我们把一个
进程连接到另一个进程的一个数据流称为一个管道。
匿名管道
#include<unistd.h>
函数原型:int pipe(int fd[2]);
功能:创建一无名管道。
参数
fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误代码。
下面我们通过文件描述符来看看进程对管道文件的读写方式:
如图为文件描述符数组与管道之间的联系,我们让子进程写,父进程读:
我们再用代码感受一下进程通过管道是怎么通信的:
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
int fd[2];
if(pipe(fd) == -1){
perror("pipe");
return -1;
}
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -2;
}
else if(pid == 0){//child write
close(fd[0]); //关闭子进程的读端
write(fd[1],"hello",5); //将hello写入管道中
close(fd[1]);
}
else{// father read
close(fd[1]); //关闭父进程的写端
char buf[10] = {0};
read(fd[0],buf,10); //读取管道中的内容并将之输出到屏幕上
printf("buf=%s\n",buf);
}
return 0;
}
我们可以看一下运行结果:
我们可以看见通过管道文件我们实现了让子进程给父进程传递信息,这就是进程间通信的一种方式。
那么我们上面为什么要在对管道进行读写时关闭掉父进程的一端和子进程对应的另外一端呢?
因为如果我们让子进程和父进程都打开读写俩端时,会让程序不知道在哪一端读取我们要传输的数据,同样,写数据也是不知道在哪端写。所以我们一般使用匿名管道,只会保留一个读端和一个写端,这样才能正常的对管道进行读写操作。
管道读写也是有规则的,本文中就不再详细描述,许多资料中都有提到。
通过上面对匿名管道的研究我们可以总结一下匿名管道的特点:
1:只能用于拥有共同祖先的进程(拥有亲缘关系的进程)之间进行通信,通常,一个管道由进程创建,然后该进程调用fork,此后父、子进程之间可应用该管道。
2:管道在进程间的通信是基于字节流的。
3:管道的生命周期随进程,进程退出,管道释放。
4:管道自带同步机制。
5:管道只能单向传输。
接下来我们看看命名管道:
匿名管道的一个限制就是只能在具有亲缘关系的进程间通信。如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
创建一个命名管道:
命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
mkfifo filename
命名管道也可以从程序里创建,相关函数有:
int mkfifo(const char *filename,mode_t mode)
创建命名管道:
int main()
{
mkfifo("p2",0644);
return 0;
}
下面我们来一段代码用命名管道实现俩个进程间的通信:
Makefile:
.PHONE:all
all:clientPipe serverPipe
clientPipe:clientPipe.c
gcc -o $@ $^
serverPipe:serverPipe.c
gcc -o $@ $^
.PHONE:clean
clean:
rm -f serverPipe clientPipe
serverPipe.c:
#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
if(mkfifo("mypipe",0644) < 0){
perror("mkfifo");
return 1;
}
int rfd = open("mypipe",O_RDONLY);
if(rfd < 0){
perror("open");
return 2;
}
char buf[1024];
while(1){
buf[0] = 0;
printf("Please wait...\n");
ssize_t s = read(rfd,buf,sizeof(buf)-1);
if(s > 0){
buf[s-1] = 0;
printf("client: %s\n",buf);
}else if(s == 0){
printf("client quit,exit now\n");
break;
}else{
perror("read");
return 3;
}
}
close(rfd);
return 0;
}
clientPipe.c:
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int wfd = open("mypipe",O_WRONLY);
if(wfd < 0){
perror("open");
}
char buf[1024];
while(1){
buf[0] = 0;
printf("Please Enter: ");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);
if(s > 0){
buf[s] = 0;
write(wfd,buf,strlen(buf));
}else if(s <= 0){
perror("read");
}
}
close(wfd);
return 0;
}
接下来我们打开来个终端运行一下:
由此我们就实现了用命名管道在俩个不相关进程之间的通信。