您的位置:首页 > 其它

进程间通信IPC—匿名管道(pipe)和命名管道(fifo)

2016-04-11 21:45 531 查看
管道内部如何实现-大小,组织方式,环形队列?

一.进程间通信有多种方式,本文主要讲解对管道的理解。管道分为匿名管道和命名管道。
(1)管道( pipe ):又称匿名管道。是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
(2)命名管道 (named pipe或FIFO) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

二.管道
1. 管道的特点:(1)管道是半双工的,数据只能向一个方向流动;双方通信时,需要建立起两个管道;
(2)只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
(3)单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。
(4)数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

2.通信步骤
2.1创建管道



利用pipe()函数创建管道,即在内核中开辟一块缓冲区(称为管道)用于通信。pipe函数调用成功返回0,调用失败返回-1。
管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道,一般文件I/O的函数都可以用来操作管道(lseek除外)。管道的读写规则如下:

从管道中读取数据:如果管道的写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0;

当管道的写端存在时,如果请求的字节数目大于PIPE_BUF,则返回管道中现有的数据字节数,如果请求的字节数目小于PIPE_BUF,返回请求的字节数(此时,管道中数据量大于请求的数据量)。注:(PIPE_BUF在include/linux/limits.h中定义,不同的内核版本可能会有所不同。Posix.1要求PIPE_BUF至少为512字节,red hat 7.2中为4096)。

向管道中写入数据:向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞。
注:只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIFPIPE信号,应用程序可以处理该信号,也可以忽略(默认动作则是应用程序终止)。

2.2创建子进程
单独创建一个无名管道,并没有实际的意义。我们再fork一个子进程,然后通过管道实现父子进程间的通信(即两个进程中存在亲缘关系,这里的亲缘关系指的是具有共同的祖先)。

2.3父进程关闭管道读端,子进程关闭管道写端

3.代码验证
3.1检验管道大小
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 int main()
5 {
6     int fd[2];
7     int count=0;
8     if(pipe(fd)<0)
9     {
10         perror("Fail to create pipe");
11         exit(EXIT_FAILURE);
12     }
13     while(1)
14     {
15         write(fd[1],"a",sizeof(char));
16         printf("count=%d.\n",++count);
17     }
18     return 0;
19 }
运行结果:65536=64K

3.2读写规则探究
A.从管道中读取数据
<1>写端不存在时

1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 int main()
5 {
6     int n;
7     int fd[2];
8     int count=0;
9     char buf[100]={0};
10
11     if(pipe(fd)<0)
12     {
13         perror("Fail to create pipe");
14         exit(EXIT_FAILURE);
15     }
16     close(fd[1]);
17
18     if((n=read(fd[0],buf,sizeof(buf)))<0)
19     {
20         perror("Fail to read pipe");
21         exit(EXIT_FAILURE);
22     }
23     printf("read %d bytes:%s\n",n,buf);
24     return 0;
25 }
运行结果:read 0 bytes:
<2>写端存在时
父进程向管道中写数据,子进程从管道中读取数据
1 #include<stdio.h>
2 #include<errno.h>
3 #include<stdlib.h>
4 #include<string.h>
5
6 #define N 10
7 #define MAX 100
8 int child_read(int fd)
9 {
10     char buf
;
11     int n=0;
12     while(1)
13     {
14         n=read(fd,buf,sizeof(buf));
15         buf
='\0';
16         printf("Read %d bytes:%s\n",n,buf);
17         if(strncmp(buf,"quit",4)==0)
18             break;
19     }
20     return 0;
21 }
22
23 int father_write(int fd)
24 {
25     char buf[MAX]={0};
26     while(1)
27     {
28         printf(">");
29         fgets(buf,sizeof(buf),stdin);
30         buf[strlen(buf)-1]='\0';
31         write(fd,buf,strlen(buf));
32         sleep(1);
33         if(strncmp(buf,"quit",4)==0)
34             break;
35     }
36     return 0;
37 }
38
39 int main()
40 {
41     int fd[2];
42     if(pipe(fd)<0)
43     {
44         perror("Fail to create pipe");
45         exit(EXIT_FAILURE);
46     }
47     pid_t pid=fork();
48     if(pid<0)
49     {
50         perror("Fail to fork");
51         exit(EXIT_FAILURE);
52     }
53     else if(pid==0)
54     {
55         close(fd[1]);
56         child_read(fd[0]);
57     }
58     else
59     {
60         close(fd[0]);
61         father_write(fd[1]);
62     }
63     exit(EXIT_SUCCESS);
64     return 0;
65
66 }
运行结果:



解释:因为buf大小为N=10,后面还加了一个'\0',超出了数组分配的内存空间(这是代码的一个错误),所以这儿的0应为10.

从以上验证我们可以看到:
<1>当写端存在时,管道中没有数据时,读取管道时将阻塞
<2>当读端请求读取的数据大于管道中的数据时,此时读取管道中实际大小的数据
<3>当读端请求读取的数据小于管道中的数据时,此时放回请求读取的大小数据

B.向管道中写入数据:
向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。当管道满时,读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞。
注意:只有管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIGPIPE信号,应用程序可以处理该信号,也可以忽略(默认动作则是使应用程序终止)。
1 #include<stdio.h>
2 #include<errno.h>
3 #include<stdlib.h>
4 #include<string.h>
5
6 int main()
7 {
8     int n=0;
9     char buf[1000*6]={0};
10     int fd[2];
11     if(pipe(fd)<0)
12     {
13         perror("Fail to create pipe");
14         exit(EXIT_FAILURE);
15     }
16     pid_t pid=fork();
17     if(pid<0)
18     {
19         perror("Fail to fork");
20         exit(EXIT_FAILURE);
21     }
22     else if(pid==0)
23     {
24         close(fd[1]);
25         sleep(3);
26         close(fd[0]);
27         printf("Read port close\n");
28         sleep(1);
29     }
30     else
31     {
32         close(fd[0]);
33         while(1)
34         {
35             n=write(fd[1],buf,sizeof(buf));
36             printf("write %d bytes to pipe\n",n);
37         }
38     }
39     exit(EXIT_SUCCESS);
40     return 0;
41 }
运行结果:




三.命名管道
1. 管道的特点:
(1)FIFO不同于管道之处在于它提供一个路径名与之关联。
(2)以FIFO的文件形式存储于文件系统中。命名管道是一个设备文件,因此,即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。
(3)FIFO(first input first output)总是按照先进先出的原则工作

2.通信步骤 2.1创建管道Linux下有两种方式创建:
(1)在Shell下交互地建立一个命名管道;(用mknod或mkfifo命令,例如:mknod namedpipe)
(2)在程序中使用系统函数建立命名管道。(系统函数有两个:mknod和mkfifo)



命名管道创建后就可以使用了,命名管道和管道的使用方法基本是相同的。只是使用命名管道时,必须先调用open()将其打开。因为命名管道是一个存在于硬盘上的文件,而管道是存在于内存中的特殊文件。

3.代码验证
//写端
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<sys/stat.h>
4 #include<unistd.h>
5 #include<fcntl.h>
6 #include<string.h>
7
8
9 #define _PATH_ "/tmp/file.tmp"
10 #define _SIZE_ 100
11
12 int main()
13 {
14     int ret=mkfifo(_PATH_,0666|S_IFIFO);
15     if(ret==-1)
16     {
17         printf("mkfifo error|n");
18         return 1;
19     }
20     int fd=open(_PATH_,O_WRONLY);
21     if(fd<0)
22     {
23         printf("open file error!\n");
24     }
25     char buf[_SIZE_];
26     memset(buf,'\0',sizeof(buf));
27     while(1)
28     {
29         scanf("%s",&buf);
30         int ret=write(fd,buf,sizeof(buf));
31         if(ret<0)
32         {
33             printf("write error!\n");
34             break;
35         }
36         if(strncmp(buf,"quit",4)==0)
37         {
38             break;
39         }
40     }
41
42     close(fd);
43     return 0;
44 }
45

//读端
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<sys/stat.h>
4 #include<unistd.h>
5 #include<fcntl.h>
6 #include<string.h>
7
8 #define _PATH_ "/tmp/file.tmp"
9 #define _SIZE_ 100
10
11 int main()
12 {
13     int fd=open(_PATH_,O_RDONLY);
14     if(fd<0)
15     {
16         printf("open file error!\n");
17         return 1;
18     }
19     char buf[_SIZE_];
20     memset(buf,'\0',sizeof(buf));
21     while(1)
22     {
23         int ret=read(fd,buf,sizeof(buf));
24         if(ret<=0)
25         {
26             printf("read end or error!\n");
27             break;
28         }
29         printf("%s\n",buf);
30         if(strncmp(buf,"quit",4)==0)
31         {
32             break;
33         }
34     }
35
36     close(fd);
37     return 0;
38 }
输出结果:


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  通信 管道 命名管道