进程间通信----(IPC)InterProcess Communication
一:管道
管道(包括无名管道和命名管道),通常指无名管道,是 UNIX 系统IPC最古老的形式。
[含义]:管道是一个进程的数据流到另一个进程的通道,即一个进程的数据的输出作为另一个进程的数据的输入,管道起到了桥梁的作用。
比如:当我们输入: ls -l | cat test .其中ls和cat是两个进程,|代表管道,意思是执行ls -l进程,并将输出结果作为cat test进程的输入,cat进程将输入的结果打印在屏幕上:
[本质]:匿名管道之所以可以通信的本质在于,父进程fork子进程,父子进程各自拥有一个文件描述符表,但是两者的内容是一样的,既然内容一致,那么指向的就是同一个管道,即父子进程看到了同一份公共资源。
它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。若要进行双向通信,需要两个管道
它只能用于具有共同祖先的进程之间的通信(也是父子进程或者兄弟进程之间)。
它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
- 一般而言,进程退出,管道释放,管道通信依赖于文件系统,所以管道的生命周期随进程
- 一般而言,内核会对管道操作进行同步和互斥,保证读写顺序一致
- 管道的通信被称为面向字节流
2、管道的读写规则
当没有数据可读时,read调用阻塞,即进程暂停执行,一直等到有数据来到为止
当管道满的时候,write调用阻塞,直到有进程读走数据.1)如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数为0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据被读取后,再次read会返回0,就像读到文件末尾一样。
2)如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写端的进程也没用向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
3)如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。
4)如果有指向管道读端的文件描述符没有关闭(管道读端的引用计数大于0),而持有管道读端的进程也没有从管道中读取数据,这是有进程向管道的写端写入数据,那么在管道被写满时,再次write会阻塞,直到管道中有空位置了才写入数据并返回。
2、原型:
1 #include <unistd.h> 2 int pipe(int fd[2]); // 返回值:若成功返回0,失败返回-1
当一个管道建立时,它会创建两个文件描述符:fd[0]
为读而打开,fd[1]
为写而打开。如下图:
要关闭管道只需将这两个文件描述符关闭即可
3、例子
单个进程中的管道几乎没有任何用处。所以,通常调用 pipe 的进程接着调用 fork,这样就创建了父进程与子进程之间的 IPC 通道。如下图所示:
fd[0]
)与子进程的写端(fd[1]
);反之,则可以使数据流从子进程流向父进程。
4.代码
1.创建管道
2.创建子进程
3.子进程写数据
4.父进程读数据
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<errno.h>
4 #include<string.h>
5 #include<stdlib.h>
6 int main(){
7 int fd[2];
8 if(pipe(fd)==-1){
9 perror("pipe");
10 return -1;
11 }
12 pid_t pid;
13 pid=fork();
14 if(pid==-1){
15 perror("fork");
16 }
17 if(pid==0){
18 close(fd[0]);
19 write(fd[1],"hello",5);
20 close(fd[1]);
21 exit(-1);
22 }
23 if(pid>0){
24 close(fd[1]);
25 char buf[10]={0};
26 read(fd[0],buf,10);
27 printf("buf=%s\n",buf);
28 }
29 return 0;
30 }
以上我们介绍的是匿名管道,现在我们来看一下命名管道
我们会发现,匿名管道有一个限制就是只能用于具有共同祖先的进程,如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它就是我们经常提到的命名管道(命名管道是一种特殊的文件类型),命名管道之所以可以实现进程间通信在于通过同一个路径名而看到同一份资源,这份资源以FIFO的文件形式存在于文件系统中。FIFO总是按照先入先出的原则工作,第一个被写入的数据将首先从管道读出。
命名管道对应磁盘上的一个文件名,一旦建立起文件名与管道的关系,删掉文件是没有关系的
1,创建命名管道
命名管道可以在命令行上创建----- $ mkfifo filename
命名管道也可以从程序里创建------- int mkfifo(const char *filename,mode_t mode);
创建命名管道
int main(int argc,char *argv[]){
mkfifo("p2",0644);
return 0;
}
2,命名管道与匿名管道的区别
匿名管道由pipe函数创建并打开
命名管道由mkfifo函数创建,打开用open
一旦这些工作做好之后,它们具有相同的语义
3,命名管道实现服务器和客户端的通信
server_pipe.c 1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<sys/stat.h>
4 #include<fcntl.h>
5 #include<unistd.h>
6 #include<stdlib.h>
7 int main(){
8 //创建命名管道
9 if(mkfifo("mypipe",0644)<0)
10 {
11 perror("mkfifo");
12 exit(1);
13 }
14 int Rfd = open("mypipe",O_RDONLY);
15 if(Rfd<0)
16 { perror("mkfifo");
17 exit(1);
18 }
19 char buf[1024];
20 while(1){
21 buf[0]=0;
22 printf("please wait...\n");
23 //服务器从文件即管道里读数据放入buf中
24 ssize_t s = read(Rfd,buf,sizeof(buf)-1);
25 if(s>0){
26 buf[s-1]=0;
27 printf("client say# %s\n",buf);
28 }
29 else if(s==0){
30 printf("client quit,exit now!\n");
31 exit(1);
32 }
33 else{
34 perror("read");
35 exit(1);
36 }
37 }
38 close(Rfd);
39 return 0;
40 }
client_pipe.c
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<sys/stat.h>
4 #include<fcntl.h>
5 #include<unistd.h>
6 #include<stdlib.h>
7 #include<string.h>
8 int main(){
9 int Wfd = open("mypipe",O_WRONLY);
10 if(Wfd<0)
11 {
12 perror("open");
13 exit(1);
14 }
15 char buf[1024];
16 while(1){
17 buf[0]=0;
18 printf("please enter# ");
19 fflush(stdout);
20 //从标准输入里读数据放到buf缓冲区中
21 ssize_t s = read(0,buf,sizeof(buf)-1);
22 if(s>0){
23 buf[s]=0;
24 //如果读到了就写入Wfd文件里
25 write(Wfd,buf,strlen(buf));
26 }
27 else if(s<=0){
28 perror("read");
29 exit(1);
30 }
31 }
32 close(Wfd);
33 return 0;
34 }