10.3 进程间通信--管道

管道基本概念:

  管道是Unix中最古老的进程间通信形式

  我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”

管道的本质:固定大小的内核缓冲区

管道限制:

  管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

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

匿名管道pipe:用在父子进程之间或有血缘关系的进程之间

  原型:int pipe(int  fd[2])

  功能:创建一个无名管道

  参数:

    fd:文件描述符数组,其中,fd[0]表示读端,fd[1]表示写端

  返回值:成功返回0,失败返回错误码

管道创建后示意图:

管道的读写规则如下:

管道读写示例:

  1 #include <unistd.h>
  2 #include <sys/stat.h>
  3 #include <sys/wait.h>
  4 #include <sys/types.h>
  5 #include <fcntl.h>
  6 
  7 #include <stdlib.h>
  8 #include <stdio.h>
  9 #include <errno.h>
 10 #include <string.h>
 11 #include <signal.h>
 12 
 13 #define ERR_EXIT(m) 
 14     do 
 15     { 
 16         perror(m); 
 17         exit(EXIT_FAILURE); 
 18     } while(0)
 19 
 20 
 21 //测试 默认情况下,管道的读写
 22 int main41(void )
 23 {
 24     int pipefd[2];
 25     pid_t pid;
 26     if (pipe(pipefd) == -1 )    
 27     {
 28         printf("pipe() err..
");    
 29         return -1;
 30     }
 31     pid = fork();
 32     if (pid == -1)
 33     {
 34         printf("fork err..
");
 35         return -1;
 36     }
 37     
 38     if (pid == 0)
 39     {
 40         printf("子进程延迟3秒后,再写。。
");
 41         sleep(3);
 42         close(pipefd[0]);
 43         write(pipefd[1], "hello hello....", 6);
 44         close(pipefd[1]);
 45         printf("child .....quit
");
 46         exit(0);
 47     } 
 48     else if (pid > 0 )
 49     {
 50         int len = 0; 
 51         char buf[100] = {0};
 52         close(pipefd[1]);
 53     
 54     
 55         printf("测试非阻塞情况下,begin read ...
");
 56         len = -1;
 57         while (len < 0)
 58         {
 59                 len = read(pipefd[0], buf, 100);
 60                 if (len == -1)
 61                 {
 62                     sleep(1);
 63                     //close(pipefd[0]);
 64                     perror("
read err:");
 65                     //exit(0);
 66                 }
 67         }
 68     
 69         printf("len:%d, buf:%s 
", len , buf);
 70         close(pipefd[0]);
 71     }
 72 
 73     wait(NULL);
 74     printf("parent ..quit
");
 75     return 0;
 76 
 77 }
 78 
 79 
 80 //文件状态设置成阻塞非阻塞
 81 int main(void )
 82 {
 83     int pipefd[2];
 84     pid_t pid;
 85     if (pipe(pipefd) == -1 )    
 86     {
 87         printf("pipe() err..
");    
 88         return -1;
 89     }
 90     pid = fork();
 91     if (pid == -1)
 92     {
 93         printf("fork err..
");
 94         return -1;
 95     }
 96     
 97     if (pid == 0)
 98     {
 99         sleep(3);
100         close(pipefd[0]);
101         write(pipefd[1], "hello hello....", 6);
102         close(pipefd[1]);
103         printf("child .....quit
");
104     } 
105     else if (pid > 0 )
106     {
107         int len = 0; 
108         char buf[100] = {0};
109         close(pipefd[1]);
110     
111     /*    
112        int fcntl(int fd, int cmd);
113        int fcntl(int fd, int cmd, long arg);
114        int fcntl(int fd, int cmd, struct flock *lock);
115 F_GETFL  F_SETFL O_NONBLOCK
116 //注意不要设置错误成F_GETFD  F_SETFD
117 
118 
119 */
120         int flags = fcntl(pipefd[0], F_GETFL);
121         flags = flags | O_NONBLOCK;
122         int ret = fcntl(pipefd[0], F_SETFL, flags);
123         if (ret == -1)
124         {
125             printf("fcntl err.
");
126             exit(0);
127         }
128         
129         //把pipefd[0]文件描述符修改成非阻塞 man fcntl
130         printf("begin read ...
");
131         len = -1;
132         while (len < 0)
133         {
134                 len = read(pipefd[0], buf, 100);
135                 if (len == -1)
136                 {
137                     sleep(1);
138                     //close(pipefd[0]);
139                     perror("read err.
");
140                     //exit(0);
141                 }
142         }
143     
144         printf("len:%d, buf:%s 
", len , buf);
145         close(pipefd[0]);
146     }
147 
148     wait(NULL);
149     printf("parent ..quit
");
150     return 0;
151 
152 }

 测试管道容量的程序:2.6.11内核之后默认是65536字节

 1 #include <unistd.h>
 2 #include <sys/stat.h>
 3 #include <sys/wait.h>
 4 #include <sys/types.h>
 5 #include <fcntl.h>
 6 
 7 #include <stdlib.h>
 8 #include <stdio.h>
 9 #include <errno.h>
10 #include <string.h>
11 #include <signal.h>
12 //测试管道容量
13 
14 #define ERR_EXIT(m) 
15     do 
16     { 
17         perror(m); 
18         exit(EXIT_FAILURE); 
19     } while(0)
20 
21 
22 void myhandle(int sig)
23 {
24     printf("recv sig:%d 
", sig);
25 }
26 
27 
28 int main(void )
29 {
30     int pipefd[2];
31     pid_t pid;
32     
33     //注册管道处理函数
34     signal(SIGPIPE, myhandle);
35     
36     if (pipe(pipefd) == -1 )    
37     {
38         printf("pipe() err..
");    
39         return -1;
40     }
41 
42     pid = fork();
43     if (pid == -1)
44     {
45         printf("fork err..
");
46         return -1;
47     }
48     
49     if (pid == 0)
50     {
51         int count = 0;
52         int ret = 0;
53         close(pipefd[0]);
54         
55         
56         //写端变成非阻塞
57         int flags = fcntl(pipefd[1], F_GETFL);
58         flags = flags | O_NONBLOCK;
59         ret = fcntl(pipefd[1], F_SETFL, flags);
60         if (ret == -1)
61         {
62             printf("fcntl err.
");
63             exit(0);
64         }
65         
66         while(1)
67         {
68             ret = write(pipefd[1] , "a", 1);    
69             if (ret == -1)
70             {
71                 perror("write pipe");
72                 break;
73             }
74             count ++;    
75         }
76         
77         printf("count:%d 

", count);
78         close(pipefd[1]);
79         
80         exit(0);
81     } 
82     else if (pid > 0 )
83     {
84     
85         sleep(4);
86         close(pipefd[0]);
87         close(pipefd[1]);
88     }
89 
90     wait(NULL);
91     printf("parent ..quit
");
92     return 0;
93 
94 }

测试管道写入的PIPE_BUF原子性:原理是两个进程向管道里面写,一个进程读,看读出的数据是否乱序

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <unistd.h>
 5 #include <sys/types.h>
 6 #include <errno.h>
 7 #include <fcntl.h>
 8 
 9 
10 #define ERR_EXIT(m) 
11         do 
12         { 
13                 perror(m); 
14                 exit(EXIT_FAILURE); 
15         } while(0)
16 
17 #define TEST_SIZE 68*1024   //68K
18 
19 
20 int main(void)
21 {
22     char a[TEST_SIZE];
23     char b[TEST_SIZE];
24 
25     memset(a, 'A', sizeof(a));
26     memset(b, 'B', sizeof(b));
27 
28     int pipefd[2];
29 
30     int ret = pipe(pipefd);
31     if (ret == -1)
32         ERR_EXIT("pipe error");
33 
34     pid_t pid;
35     pid = fork();
36     if (pid == 0) //A子进程写68K数据A
37     {
38         close(pipefd[0]);
39         ret = write(pipefd[1], a, sizeof(a));
40         printf("apid=%d write %d bytes to pipe
", getpid(), ret);
41         exit(0);
42     }
43 
44     pid = fork();
45 
46     
47     if (pid == 0) //B子进程写68K数据B
48     {
49         close(pipefd[0]);
50         ret = write(pipefd[1], b, sizeof(b));
51         printf("bpid=%d write %d bytes to pipe
", getpid(), ret);
52         exit(0);
53     }
54 
55 
56     close(pipefd[1]);
57     
58     sleep(1);
59     int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
60     char buf[1024*4] = {0};
61     int n = 1;
62     while (1)
63     {
64         //父进程4k 4k的读数据,发现AB进程是交叉的写数据到管道。
65         //多个进程往管道里面,写数据。
66         ret = read(pipefd[0], buf, sizeof(buf)); 
67         if (ret == 0)
68             break;
69         printf("n=%02d pid=%d read %d bytes from pipe buf[4095]=%c
", n++, getpid(), ret, buf[4095]);
70         write(fd, buf, ret);
71 
72     }
73     return 0;    
74 }

自己实现管道:

 1 #include <unistd.h>
 2 #include <sys/stat.h>
 3 #include <sys/wait.h>
 4 #include <sys/types.h>
 5 #include <fcntl.h>
 6 
 7 #include <stdlib.h>
 8 #include <stdio.h>
 9 #include <errno.h>
10 #include <string.h>
11 #include <signal.h>
12 
13 #define ERR_EXIT(m) 
14     do 
15     { 
16         perror(m); 
17         exit(EXIT_FAILURE); 
18     } while(0)
19 
20 int main21(void )
21 {
22     int pipefd[2];
23     pid_t pid;
24     if (pipe(pipefd) == -1 )    
25     {
26         printf("pipe() err..
");    
27         return -1;
28     }
29     pid = fork();
30     if (pid == -1)
31     {
32         printf("fork err..
");
33         return -1;
34     }
35     if (pid == 0)
36     {
37         close(pipefd[0]);
38         //复制文件描述符pipefd[1],给标准输出,言外之意:execlp的ls命令输出到管道中
39         dup2(pipefd[1], STDOUT_FILENO);
40         close(pipefd[1]);
41         
42         execlp("ls", "ls", NULL);
43         //如果替换新的进程印象失败,则会执行下面一句话    
44         sprintf(stderr, "execute the cmd ls err..
");
45         exit(0);    
46     
47     
48     } 
49     else if (pid > 0 )
50     {
51         int len = 0; 
52         char buf[100] = {0};
53         close(pipefd[1]);
54         //复制文件描述符pipefd[0],给标准输入,言外之意:execlp的wc命令从管道中读
55         dup2(pipefd[0], STDIN_FILENO);
56         close(pipefd[0]);
57         //len = read(pipefd[0], buf, 100);
58         execlp("wc", "wc", "-w", NULL);
59         printf("len:%d, buf:%s 
", len , buf);
60 
61         //close(pipefd[0]);
62     }
63 
64     wait(NULL);
65     printf("parent ..quit
");
66     return 0;
67 
68 }
69 
70 
71 int main(int argc, char *argv[])
72 {
73     close(0); //关闭标准输入
74     open("makefile", O_RDONLY); //makefile文件变成标准输入
75     close(1);//关闭标准输出
76     open("makefile2", O_WRONLY | O_CREAT | O_TRUNC, 0644); //maifle2变成标准输出
77 
78     execlp("cat", "cat", NULL); //替换进程印象后,执行cat命令
79     
80     //cat命令 从标准输入中按行读,紧接着写到标准输出
81 
82     return 0;
83     
84 }

 改变文件系统描述符实现文件复制:

命名管道FIFO:可以用在不相关的进程之间

 

匿名管道由pipe函数创建和打开,而命名管道由mkfifo创建,由open打开,相当于创建和打开时分开的。

示例如下:

写管道:

读管道:

原文地址:https://www.cnblogs.com/wanmeishenghuo/p/9426865.html