管道的概念:
管道是一种最基本的IPC机制,由pipe函数创建:
#include<unistd.h>
int pipe(int fileds[2]);
调用pipe函数时在内核中开辟一块缓冲区用于通信,它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端。
进程在管道间通信:
1.父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。
2.父进程调用fork创建子进程,子进程有两个文件描述符指向同一管道。
3.父进程关闭管道读端,子进程关闭管道斜段。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入,从读端流出,这样就实现了进程间通信。
代码实现如下:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<errno.h>
4 #include<string.h>
5
6 int main()
7 {
8 int _pipe[2];
9 int ret = pipe(_pipe);
10 if(ret == -1){
11 printf("create pipe erro!errno code is:%d\n",errno);
12 return 1;
13 }
14 pid_t id=fork();
15 if(id<0){
16 printf("fork error!");
17 return 2;
18 }else if(id == 0){
19 close(_pipe[0]);
20 int i=0;
21 char* _mesg_c=NULL;
22 while(i<100){
23 _mesg_c="I am a child!";
24 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
25 sleep(1);
26 i++;
27 }
28 }else{//father
29 close(_pipe[1]);
30 char _mesg[100];
31 int j=0;
32 while(j<100) {
33 memset(_mesg,'\0',sizeof(_mesg));
34 read(_pipe[0],_mesg,sizeof(_mesg));
35 printf("%s\n",_mesg);
36 j++;
37 }
38 }
39 return 0;
40
41
42 }
程序运行结果如下:
管道的四个特殊情况:
1.所有指向管道写端的文件描述符都关闭了,仍然有进程从管道的读端读取数据,当管道中剩余的数据都被读取后,再次读取会返回0,就像读到文件末尾一样。
代码实现:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<errno.h>
4 #include<string.h>
5 #include<sys/wait.h>
6 int main()
7 {
8 int _pipe[2];
9 int ret = pipe(_pipe);
10 if(ret == -1){
11 printf("create pipe erro!errno code is:%d\n",errno);
12 return 1;
13 }
14 pid_t id=fork();
15 if(id<0){
16 printf("fork error!");
17 return 2;
18 }else if(id == 0){//child
19 close(_pipe[0]);
20 int i=0;
21 char* _mesg_c=NULL;
22 while(i<10){
23 _mesg_c="I am a child!";
24 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
25 sleep(1);
26 i++;
27 }
28 close(_pipe[1]);
29 }else{//father
30 close(_pipe[1]);
31 char _mesg[100];
32 int j=0;
33 while(j<100) {
34 memset(_mesg,'\0',sizeof(_mesg));
35 int ret = read(_pipe[0],_mesg,sizeof(_mesg));
36 printf("%s:code is:%d\n",_mesg,ret);
37 j++;
38 }
39 if(waitpid(id,NULL,0)<0)
40 {
41 return 3;
42 }
43 }
44 return 0;
45 }
运行结果如下:
2.指向管道写端的文件描述符没关闭,持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读取数据,当管道中剩余的数据都被读取后,再次读取会阻塞,直到管道中有数据可读了才读取数据并返回。
代码实现:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<errno.h>
4 #include<string.h>
5 #include<sys/wait.h>
6
7 int main()
8 {
9 int _pipe[2];
10 int ret = pipe(_pipe);
11 if(ret == -1){
12 printf("create pipe erro!errno code is:%d\n",errno);
13 return 1;
14 }
15 pid_t id=fork();
16 if(id<0){
17 printf("fork error!");
18 return 2;
19 }else if(id == 0){//child
20 close(_pipe[0]);
21 int i=0;
22 char* _mesg_c=NULL;
23 while(i<20){
24 if(i<10){
25 _mesg_c="I am a child!";
26 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
27 }
28 sleep(1);
29 i++;
30 }
31 close(_pipe[1]);
32 }else{//father
33 close(_pipe[1]);
34 char _mesg[100];
35 int j=0;
36 while(j<20) {
37 memset(_mesg,'\0',sizeof(_mesg));
38 int ret = read(_pipe[0],_mesg,sizeof(_mesg));
39 printf("%s:code is:%d\n",_mesg,ret);
40 j++;
41 }
42 if(waitpid(id,NULL,0)<0)
43 {
44 return 3;
45 }
46 }
47 return 0;
48 }
运行结果:
3.如果所有指向管道读端的文件描述符都关闭了,这时有进程向管道的写端write,该进程会收到信号SIGPIPE,会导致进程异常终止。
代码实现:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<errno.h>
4 #include<string.h>
5 #include<sys/wait.h>
6
7 int main()
8 {
9 int _pipe[2];
10 int ret = pipe(_pipe);
11 if(ret == -1){
12 printf("create pipe erro!errno code is:%d\n",errno);
13 return 1;
14 }
15 pid_t id=fork();
16 if(id<0){
17 printf("fork error!");
18 return 2;
19 }else if(id == 0){//child
20 close(_pipe[0]);
21 int i=0;
22 char* _mesg_c=NULL;
23 while(i<20){
24 if(i<10){
25 _mesg_c="I am a child!";
26 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
27 }
28 sleep(1);
29 i++;
30 }
31 }else{//father
32 close(_pipe[1]);
33 char _mesg[100];
34 int j=0;
35 while(j<3) {
36 memset(_mesg,'\0',sizeof(_mesg));
37 int ret = read(_pipe[0],_mesg,sizeof(_mesg));
38 printf("%s:code is:%d\n",_mesg,ret);
39 j++;
40 }
41 close(_pipe[0]);
42 sleep(10);
43 if(waitpid(id,NULL,0)<0)
44 {
45 return 3;
46 }
47 }
48 return 0;
49 }
运行结果:
4.如果有指向管道读端的文件描述符没关闭,而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。
代码实现:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<errno.h>
4 #include<string.h>
5 #include<sys/wait.h>
6
7 int main()
8 {
9 int _pipe[2];
10 int ret = pipe(_pipe);
11 if(ret == -1){
12 printf("create pipe erro!errno code is:%d\n",errno);
13 return 1;
14 }
15 pid_t id=fork();
16 if(id<0){
17 printf("fork error!");
18 return 2;
19 }else if(id == 0){//child
20 // close(_pipe[0]);
21 int i=0;
22 char* _mesg_c=NULL;
23 while(i<20){
24 // if(i<10){
25 _mesg_c="I am a child!";
26 write(_pipe[1],_mesg_c,strlen(_mesg_c)+1);
27 // }
28 sleep(1);
29 i++;
30 }
31 close(_pipe[1]);
32 }else{//father
33 close(_pipe[1]);
34 char _mesg[100];
35 int j=0;
36 while(j<3) {
37 memset(_mesg,'\0',sizeof(_mesg));
38 int ret = read(_pipe[0],_mesg,sizeof(_mesg));
39 printf("%s:code is:%d\n",_mesg,ret);
40 j++;
41 }
42 // close(_pipe[0]);
43 sleep(10);
44 if(waitpid(id,NULL,0)<0)
45 {
46 return 3;
47 }
48 }
49 return 0;
50 }
运行结果:
管道通信的特点:
1.它只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程之间) 。
2.它是一个单向通信的通信模式,具有固定的读端和写端。
3.管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的 read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
命名管道:
管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间通信,在命名管道(namedpipe或FIFO)提出后,该限制得到了克服。
FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。值得注意的是, FIFO严格遵循先进先出(firstinfirstout),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。
系统调用形式:
#include<sys/types.h>
#include<sys/stat.h>
int mknod(const char*path,mode_t mod,dev_t dev)
int mkfifo(const char*path,mode_t mode)
该函数的第一个参数是一个路径名,也就是创建后FIFO的名字。第二个参数与打开普通文件的open(),函数中的mode参数相同,有以下几种:
O_RDONLY:读管道
O_WRONLY:写管道
O_RDWR:读写管道
O_NONBLOCK:非阻塞
O_CREAT:
O_EXCL:
一般文件的I/O函数都可以用于FIFO,如close、read、write等等。
FIFO读规则
约定:如果一个进程为了从FIFO中读取数据而阻塞打开FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。
– 如果有进程写打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。
– 对于设置了阻塞标志的读操作说,造成阻塞的原因有两种:
A.当前FIFO内有数据,但有其它进程在读这些数据;
B.另外就是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论写入数据量的大小,也不论读操作请求多少数据量。读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)。如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞。
FIFO写规则
约定:如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。
对于设置了阻塞标志的写操作:
– 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。
– 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。
对于没有设置阻塞标志的写操作:
– 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。
– 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写;
代码实现:
fiforead.c
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 #define _PATH_"/tmp/file.tmp"
8 #define _SIZE_ 100
9
10 int main()
11 {
12 int fd = open(_PATH_,O_RDONLY);
13 if(fd<0){
14 printf("open file error!\n");
15 return 1;
16 }
17 char buf[_SIZE_];
18 memset(buf,'\0',sizeof(buf));
19 while(1){
20 int ret = read(fd,buf,sizeof(buf));
21 if(ret<=0)//error or end of file
22 {
23 printf("read end or error!\n");
24 break;
25 }
26 }
27 close(fd);
28 return 0;
29 }
fifowrite.c
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 #define _PATH_"/tmp/file.tmp"
8 #define _SIZE_ 100
9
10 int main()
11 {
12 int ret=mkfifo(_PATH_,0666|S_IFIFO);
13 if(ret==-1){
14 printf("mkfifo error\n");
15 return 1;
16 }
17
18 int fd = open(_PATH_,O_RDONLY);
19 if(fd<0){
20 printf("open file error!\n");
21 return 1;
22 }
23 char buf[_SIZE_];
24 memset(buf,'\0',sizeof(buf));
25 while(1){
26 scanf("%s",buf);
27 int ret = write(fd,buf,sizeof(buf)+1);
28 if(ret<=0)//error or end of file
29 {
30 printf("write error!\n");
31 break;
32 }
33 if(strncmp(buf,"quit",4)==0){
34 break;
35 }
36 }
37 close(fd);
38 return 0;
39 }
转载于:https://blog.51cto.com/760470897/1763616