您的位置:首页 > 其它

进程间通信【管道】

2018-03-17 22:18 393 查看

一、概念

1、基本概念

管道是一种最基本的IPC机制,作用于两个进程之间,完成数据传递;管道是Unix中最古老的进程间通信的形式。

2、管道的特点

(1)只能用于具有共同祖先的进程(具有亲缘关系的进程)之间通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后,父子进程之间就可应用该管道

(2)管道的进程间通信是基于字节流的

(3)管道是基于文件形式的,自带同步互斥机制,并且只能进行单向传输

(4)一般而言,进程退出,管道释放,所以管道的生命周期是随进程的

二、管道的分类

1、匿名管道(pipe)

匿名管道用于实现具有亲缘关系的进程间通信,通常用于父子间进程

(1)匿名管道的创建函数



注释:调用pipe函数时,首先在内核中开辟一块缓冲区用于通信,它有一个读端和一个写端,然后通过pipefd参数传出给用户进程两个文件描述符,pipefd[0]指向管道的读端,pipefd[1]指向管道的写段。在用户层面看来,打开管道就是打开了一个文件,通过read()或者write()向文件内读写数据,读写数据的实质也就是往内核缓冲区读写数据。

返回值:成功返回0,失败返回-1。

(2)匿名管道实现

那么如何实现父子间进程呢?通常有以下步骤:

1. 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。

2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。

3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。



(3)下面举一个例子说明匿名管道的创建:

mypipe.c

1 #include<unistd.h>
2 #include<stdlib.h>
3 #include<error.h>
4 #include<string.h>
5 #include<stdio.h>
6 int main(int argc,char*argv[])
7 {
8     int pipefd[2];
9     if(pipe(pipefd)==-1)
10         perror("pipe error");
11     pid_t pid;
12     pid=fork();
13     if(pid==-1)
14         perror("fork errpr");
//子进程进行写操作
15     if(pid==0)
16     {
17         close(pipefd[0]);
18         write(pipefd[1],"hello",5);
19         close(pipefd[1]);
20         exit(1);
21     }
//父进程进行读操作
22     close(pipefd[1]);
23     char buf[10]={0};
24     read(pipefd[0],buf,10);
25     printf("buf=%s\n",buf);
26
27     return 0;
28 }


运行结果:


3、读写规则

(1)如果所有指向管道写端的文件描述符都关闭了,而仍然有进程从管道的读端 读数据,那么文件内的所有内容被读完后再次read就会返回0,就像读到文件结尾。

(2)如果有指向管道写端的文件描述符没有关闭(管道写段的引用计数大于0),而持有管道写端的进程没有向管道内写入数据,假如这时有进程从管道读端读数据,那么读完管道内剩余的数据后就会阻塞等待,直到有数据可读才读取数据并返回。

(3)如果所有指向管道读端的文件描述符都关闭,此时有进程通过写端文件描述符向管道内写数据时,则该进程就会收到SIGPIPE信号,并异常终止。

(4)如果有指向管道读端的文件描述符没有关闭(管道读端的引用计数大于0),而持有管道读端的进程没有从管道内读数据,假如此时有进程通过管道写段写数据,那么管道被写满后就会被阻塞,直到管道内有空位置后才写入数据并返回。

2、命名管道(fifo)

上面介绍管道应用有两个局限性:

a、只能在具有亲缘关系的进程间通信

b、它只能实现一个进程写另一个进程读,而如果需要两者同时进行时,就得重新打开一个管道。

为了解决这一问题,我们可以使用FIFO文件来做这项工作,他经常被称为命名管道,命名管道是一种特殊类型的文件

(1)FIFO文件的两个属性:

1、FIFO是一个设备文件,在文件系统中以文件名的形式存在,因此即使进程与创建FIFO的进程不存在血缘关系也依然可以通信,前提是可以访问该路径。

2、FIFO(first input first output)总是遵循先进先出的原则,即第一个进来的数据会第一个被读走。

(2)创建命名管道

(1)直接运用命令创建



(2)运用函数接口创建

int mkfifo(const char*filename,mode_mode)

若创建成功返回0,否则返回-1

例子–用命名管道serverPipe与clientPipe之间的通信

serverPipe.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
8 #define ERR_EXIT(m) \
9         do{\
10                 perror(m);\
11                 exit(EXIT_FAILURE);\
12         }while(0)
13
14 int main()
15 {
16         umask(0);
17         if(mkfifo("mypipe",0644)<0)
18         {
19                 ERR_EXIT("mkfifo");
20         }
21         int rfd=open("mypipe",O_RDONLY);
22         if(rfd<0)
23         {
24                 ERR_EXIT("open");
25         }
26        char buf[1024];
27         while(1)
28         {
29                 buf[0]=0;
30                 printf("Please wait...\n");
31                 ssize_t s=read(rfd,buf,sizeof(buf)-1);
32                 if(s>0)
33                 {
34                         buf[s-1]=0;
35                         printf("client say#%s\n",buf);
36                 }
37                 else if(s==0)
38                 {
39                         printf("Client quit,exit now!\n");
40                         exit(EXIT_SUCCESS);
41                 }
42                 else
43                         ERR_EXIT("read");
44
45         }
46         close(rfd);
47         return 0;
48 }


clientPipe.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
9 #define ERR_EXIT(m) \
10         do{\
11                 perror(m);\
12                 exit(EXIT_FAILURE);\
13         }while(0)
14
15 int main()
16 {
17         int wfd=open("mypipe",O_WRONLY);
18         if(wfd<0)
19         {
20                 ERR_EXIT("open");
21         }
22         char buf[1024];
23         while(1)
24         {
25                 buf[0]=0;
26                 printf("Please Enter# ");
27                 fflush(stdout);
28                 ssize_t s=read(0,buf,sizeof(buf)-1);
29                 if(s>0){
30                         buf[s]=0;
31                         write(wfd,buf,strlen(buf));
32                 }
33                 else if(s<=0)
34                         ERR_EXIT("read");
35         }
36         close(wfd);
37         return 0;
38 }


makefile

1 #cat Makefile
2 .PHONY:all
3 all:serverPipe clientPipe
4 serverPipe:serverPipe.c
5          gcc -o serverPipe serverPipe.c
6 clientPipe:clientPipe.c
7         gcc -o clientPipe clientPipe.c
8 .PHONY:clean
9 clean:
10         rm -f serverPipe clientPipe
11
~


运行结果:

实现两个进程间的通信需要我们打开两个终端,便于观察

clientPipe 负责写入数据:



serverPipe负责读数据



(3)命名管道与匿名管道的区别

命名管道创建完成后就可以使用,其使用方法与管道一样;

区别在于:

(1)命名管道使用之前需要使用open()打开。这是因为:命名管道是设备文件,它是存储在硬盘上的,而管道是存在内存中的特殊文件。

(2)但是需要注意的是,命名管道调用open()打开有可能会阻塞,但是如果以读写方式(O_RDWR)打开则一定不会阻塞;

(3)以只读(O_RDONLY)方式打开时,调用open()的函数会被阻塞直到有数据可读;

(4)如果以只写方式(O_WRONLY)打开时同样也会被阻塞,知道有以读方式打开该管道。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: