四十七、进程间通信——管道的协同进程和读写特性

47.1 协同进程

47.1.1 介绍

  两个进程通过两个管道进行双向通信称为协同进程。

47.1.2 例子

  

  co_process.c

 1 #include <sys/wait.h>
 2 #include <sys/types.h>
 3 #include <unistd.h>
 4 #include <stdio.h>
 5 #include <stdlib.h>
 6 
 7 int main(void)
 8 {
 9     int fda[2], fdb[2];
10 
11     /** 创建两个管道 */
12     if((pipe(fda) < 0) || (pipe(fdb) < 0)){
13         perror("pipe error");
14         exit(1);
15     }
16 
17     pid_t pid;
18     pid = fork();
19     if(pid < 0){
20         perror("fork error");
21     }
22     else if(pid == 0){
23         /** 子进程负责从管道 a 中读取父进程写入的累加参数 x 和 y
24          *  通过 exec 函数去调用 bin/add 程序进行累加
25          *  将累加结果写入管道 b */
26         close(fda[1]);
27         close(fdb[0]);
28 
29         /** 将标准输入重定向到管道 a 的读端 */
30         /** add 将从管道 a 中读取累加参数 x 和 y */
31         if(dup2(fda[0], STDIN_FILENO) != STDIN_FILENO){
32             perror("dup2 error");
33         }
34         if(dup2(fdb[1], STDOUT_FILENO) != STDOUT_FILENO){
35             perror("dup2 error");
36         }
37 
38         if(execlp("bin/add", "bin/add", NULL) < 0){
39             perror("execlp error");
40             exit(1);
41         }
42 
43         close(fda[0]);
44         close(fdb[1]);
45     }
46     else {
47         /** 从标准输入上读取累加参数 x 和 y
48          *  将 x 和 y 写入管道 a
49          *  从管道 b 中读取累加的结果并输出 */
50         close(fda[0]);
51         close(fdb[1]);
52         int x,y;
53         printf("Please input x and y:");
54         scanf("%d %d", &x, &y);
55 
56         if(write(fda[1], &x, sizeof(int)) != sizeof(int)) {
57             perror("write error");
58         }
59         if(write(fda[1], &y, sizeof(int)) != sizeof(int)) {
60             perror("write error");
61         }
62 
63         int result = 0;
64         if(read(fdb[0], &result, sizeof(int)) != sizeof(int)){
65             perror("read error");
66         }
67         else {
68             printf("add result is %d\n", result);
69         }
70 
71         close(fda[1]);
72         close(fdb[0]);
73         wait(0);
74     }
75 
76     return 0;
77 }

  add.c

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 
 6 int main(void)
 7 {
 8     int x, y;
 9 
10     if(read(STDIN_FILENO, &x, sizeof(int)) < 0) {
11         perror("read error");
12     }
13 
14     if(read(STDIN_FILENO, &y, sizeof(int)) < 0) {
15         perror("read error");
16     }
17 
18     int result = x + y;
19 
20     if(write(STDOUT_FILENO, &result, sizeof(int)) != sizeof(int)){
21         perror("write error");
22     }
23 
24     exit(0);
25 }

  先编译 add.c ,再编译 co_process.c,运行结果如下:

  

47.2 读写特性

47.2.1 介绍

  • 通过打开两个管道来创建一个双向管道
  • 管道是阻塞性的,当进程从管道中读取数据,若没有数据,进程会阻塞
  • 当一个进程往管道中不断地写入数据,但是没有进程去读取数据,此时只要管道是没有满是可以的,但若管道放满数据的,则会报错
  • 不完整管道:
    • 当读一个写端已经被关闭的管道时,在所有数据被读取后,read 返回 0,以表示到达了文件尾部。
    • 如果写一个读端已被关闭的管道,则产生信号 SIGPIPE,如果忽略该信号或捕捉该信号并从处理程序返回,则 write 返回 -1,同时 errno 设置为 EPIPE

47.2.2 例子

 例子1,读取写端关闭的不完整管道

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <unistd.h>
 5 #include <sys/types.h>
 6 #include <sys/wait.h>
 7 
 8 /** 不完整管道:读取一个写端已经关闭的管道 */
 9 int main(void)
10 {
11     int fd[2];
12 
13     if(pipe(fd) < 0){
14         perror("pipe error");
15         exit(1);
16     }
17 
18     pid_t pid;
19     if((pid = fork()) < 0){
20         perror("fork error");
21         exit(1);
22     }
23     else if(pid > 0){
24         /** 父进程从不完整管道(写端关闭)中读取数据 */
25         sleep(5); ///< 等子进程将管道的写端关闭
26         close(fd[1]);
27         while(1){
28             char c;
29             if(read(fd[0], &c, 1) == 0){
30                 printf("\nwrite-end of pipe closed\n");
31                 break;
32             }
33             else {
34                 printf("%c", c);
35             }
36         }
37 
38         close(fd[0]);
39         wait(0);
40     }
41     else {
42         /** 子进程负责将数据写入管道 */
43         close(fd[0]);
44         char *s = "1234";
45         write(fd[1], s, sizeof(s));
46         /** 写入数据后关闭管道的写端 */
47         close(fd[1]);
48     }
49 
50     return 0;
51 }

  编译运行结果如下:

  

例子2,写一个写端被关闭的不完整管道

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <unistd.h>
 5 #include <sys/types.h>
 6 #include <sys/wait.h>
 7 #include <errno.h>
 8 
 9 /** 不完整管道: 写一个写端已经关闭的管道 */
10 
11 void sig_handler(int signo)
12 {
13     if(signo == SIGPIPE){
14         printf("SIGPIPE occured\n");
15     }
16 }
17 
18 int main(void)
19 {
20     int fd[2];
21 
22     if(pipe(fd) < 0){
23         perror("pipe error");
24         exit(1);
25     }
26 
27     pid_t pid;
28     if((pid = fork()) < 0){
29         perror("fork error");
30         exit(1);
31     }
32     else if(pid > 0){
33         /** 父进程负责将数据写如到不完整管道(读端关闭)中 */
34         sleep(5); ///< 等子进程将管道的读端关闭
35         close(fd[0]);
36         if(signal(SIGPIPE, sig_handler) == SIG_ERR){
37             perror("signal sigpipe error");
38             exit(1);
39         }
40 
41         char *s = "1234";
42         if(write(fd[1], s, sizeof(s)) != sizeof(s)){
43             fprintf(stderr, "%s, %s\n", strerror(errno),
44                     (errno == EPIPE) ? "EPIPE" : ", unknown");
45 
46         }
47         close(fd[1]);
48         wait(0);
49     }
50     else {
51         /** 子进程关闭管道的读端 */
52         close(fd[0]);
53         close(fd[1]);
54     }
55 
56     return 0;
57 }

  编译运行:

  

 

posted @ 2019-01-19 16:37  游戏进行中  阅读(537)  评论(1编辑  收藏  举报