• Linux进程间通信-管道(pipe)


    本系列文章主要是学习记录Linux下进程间通信的方式。

    常用的进程间通信方式:管道、FIFO、消息队列、信号量以及共享存储。

    参考文档:《UNIX环境高级编程(第三版)》

    参考视频:Linux进程通信  推荐看看,老师讲得很不错

    Linux核心版本:2.6.32-431.el6.x86_64

    注:本文档只是简单介绍IPC,更详细的内容请查看参考文档和相应视频。

    本文介绍利用管道进行进程间的通信。

    1  简介

    管道是最古老的一种方式,局限性:

    • 半双工方式,数据只能在一个方向上流动;
    • 只能在具有公共祖先的两个进程间使用。

    2  函数接口

    1 #include <unistd.h>
    2 int pipe(int pipefd[2]);
    3 说明:创建一个pipe
    4 返回值:成功返回0,出错返回-1
    5 参数[out]:fd保存返回的两个文件描述符,fd[0]为读而打开,fd[1]为写而打开。fd[1]的输出是fd[0]的输入。

    3  通信模型

    通信模型一:进程先调用pipe,接着调用fork,从而创建从父进程到子进程的IPC通道。

    通信模型二:从父进程到子进程的通道。父进程关闭管道的读端(fd[0]),子进程关闭写端(fd[1])。

     

    当管道一端被关闭后,以下两条规则起作用:

    当读(read)一个写端已被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束。

    如果写(write)一个读端已被关闭的管道,则产生信号SIGPIPE。

     4  读写特性

    1)可通过打开两个管道来创建一个双向的管道;

    2)管道是阻塞性的,当进程从管道中读取数据,若没有数据进程会阻塞;

    3)当一个进程往管道中不断的写入数据但是没有进程去读取数据,此时只要管道没有满是可以的,但若管道满的则会报错。

     5  测试代码

     (1)实例1

    创建一个从父进程到子进程的管道,并且父进程通过该管道向子进程传送数据。

     1 #include <stdio.h>
     2 #include <unistd.h>
     3 
     4 #define MAXLINE 512
     5 
     6 int main(void)
     7 {
     8     int n;
     9     int fd[2];
    10     pid_t pid;
    11     char line[MAXLINE];
    12 
    13     if (pipe(fd) < 0) {  //创建管道
    14         perror("pipe error!");
    15         return -1;
    16     }
    17     if ((pid = fork()) < 0) {  //创建子进程
    18         perror("fork error!");
    19         return -1;
    20     } else if (pid > 0) {  //父进程
    21         close(fd[0]);    //父进程关闭读管道
    22         write(fd[1], "hello world
    ", 12);  //父进程向管道中写入数据
    23         close(fd[1]);
    24         wait(0);  //等待子进程结束
    25     } else {  //子进程
    26         close(fd[1]);  //子进程关闭写管道
    27         n = read(fd[0], line, MAXLINE);  //子进程从管道中读取数据
    28         write(STDOUT_FILENO, line, n);   //标准输出
    29         close(fd[0]);
    30     }
    31 
    32     return 0;
    33 }
    View Code

    (2)实例2

    使用pipe实现类似于:cat /etc/passwd | grep root这个命令。

     1 #include <stdio.h>
     2 #include <unistd.h>
     3 #include <stdlib.h>
     4 
     5 char *cmd1[3] = {"/bin/cat", "/etc/passwd", NULL};
     6 char *cmd2[3] = {"/bin/grep", "root", NULL};
     7 
     8 int main(void)
     9 {
    10     int fd[2];
    11     int i = 0;
    12     pid_t pid;
    13 
    14     if (pipe(fd) < 0) {
    15         perror("pipe error");
    16         exit(1);
    17     }
    18 
    19     for (i = 0; i < 2; i++) {
    20         pid = fork();
    21         if (pid < 0) {
    22             perror("fork error");
    23             exit(1);
    24         } else if (pid == 0) {
    25             if (i == 0) {  //第一个子进程
    26                 //负责往管道写入数据
    27                 close(fd[0]);  //关闭读端
    28                 //cat命令执行结果是标准输出,需要将标准输出重定向到管道写端
    29                 //下面命令执行的结果会写入到管道中,而不是输出到屏幕
    30                 if (dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO) {
    31                     perror("dup2 error");
    32                     exit(1);
    33                 }
    34                 close(fd[1]);  //已经复制了一份,原来的可以关闭
    35                 //调用exce函数执行cat命令
    36                 if (execvp(cmd1[0], cmd1) < 0) {
    37                     perror("execvp error");
    38                     exit(1);
    39                 }
    40                 break;
    41             }
    42             if (i == 1) {  //第二个子进程
    43                 //负责从管道读取数据
    44                 close(fd[1]);  //关闭写端
    45                 //grep命令默认读取的内容来源于标准输入
    46                 //需要将标准输入重定向到管道的读端
    47                 //下面命令执行时从管道的读端读取内容,而不是从标准输入读取
    48                 if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO) {
    49                     perror("dup2 error");
    50                     exit(1);
    51                 }
    52                 close(fd[0]);
    53                 //调用exce函数执行grep命令
    54                 if (execvp(cmd2[0], cmd2) < 0) {
    55                     perror("execvp error");
    56                     exit(1);
    57                 }
    58                 break;
    59             }
    60         } else {  //父进程,仅用于创建子进程
    61             //等待子进程创建并回收
    62             if (i == 1) {
    63                 //等待子进程全部创建完毕,才回收
    64                 close(fd[0]);
    65                 close(fd[1]);
    66                 wait(0);
    67                 wait(0);
    68             }
    69         }
    70     }
    71 
    72     return 0;
    73 }
    View Code

    (3)实例3

    使用pipe实现一个协同进程。

     创建两个管道,父进程向管道1中写入数据,并从管道2中读取子进程计算出的结果值;

    子进程从管道1中读取数据,并调用add程序进行累加,将累加的结果写入到管道2中。

    add程序实现代码:

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

    协同进程实现代码:

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

    测试结果:

    [root@192 ipc]# gcc -o bin/add add.c
    [root@192 ipc]# gcc -o bin/co_pro c_process.c
    [root@192 ipc]# ./bin/co_pro
    please input x and y: 12 23
    add result is 35

    (4)案例4

    实现一个不完整管道:当读一个写端已被关闭的管道时,在所有数据被读取后,read返回0,以表示到达了文件尾部。

     1 #include <stdio.h>
     2 #include <unistd.h>
     3 #include <stdlib.h>
     4 
     5 /*
     6  * 不完整管道:读取一个写端已经关闭的管道
     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     pid_t pid;
    18     if ((pid = fork()) < 0) {
    19         perror("fork error");
    20         exit(1);
    21     } else if (pid > 0) {  //父进程
    22         //父进程从不完整管道(写端关闭)中读取数据
    23         sleep(5);  //等子进程将管道写端关闭
    24         close(fd[1]);
    25         while (1) {
    26             char c;
    27             if (read(fd[0], &c, 1) == 0) {
    28                 printf("
    write-end of pipe closed
    ");
    29                 break;
    30             } else {
    31                 printf("%c", c);
    32             }
    33         }
    34     } else {  //子进程
    35         // 子进程负责将数据写入管道
    36         close(fd[0]);
    37         char *s = "1234";
    38         write(fd[1], s, sizeof(s));
    39         close(fd[1]);
    40     }
    41 
    42     return 0;
    43 }
    View Code

    (5)案例5

    实现一个不完整管道:当写一个读端被关闭的信号,则产生信号SIGPIPE,如果忽略该信号或捕捉该信号并从处理程序返回,则write返回-1,同时errno设置为EPIPE。

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

    6  标准库中的管道操作

    函数实现的操作:创建一个管道,fork一个子进程,关闭未使用的管道端,执行一个shell运行命令,然后等待命令终止。

    (1)函数原型

    1 #include <stdio.h>
    2 FILE *popen(const char *command, const char *type);
    3 返回值:成功返回文件指针,出错返回NULL。
    4 参数command:命令的路径。
    5 参数type:读写特性,”r”或”w”
    6 int pclose(FILE *stream);

    函数popen先执行fork,然后调用exec执行command,并且返回一个标准I/O文件指针。

    如果type是“r”,则文件指针连接到command的标准输出。

    如果type是"w",则文件指针连接到command的标准输入。

    (2)popen内部原理

     (3)实例

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 
     4 int main(void)
     5 {
     6     FILE *fp;
     7 
     8     //命令执行的结果放置到fp指向的结构体缓存中
     9     fp = popen("cat /etc/passwd", "r");
    10     char buf[512] = {0};
    11     while (fgets(buf, sizeof(buf), fp) != NULL) {
    12         printf("%s", buf);
    13     }
    14     pclose(fp);
    15 
    16     printf("----------------------------------
    ");
    17     //为wr命令提供统计的数据
    18     fp = popen("wc -l", "w");
    19     fprintf(fp, "1
    2
    3
    ");
    20     pclose(fp);
    21 
    22     return 0;
    23 }
    View Code
  • 相关阅读:
    超强问卷调查系统源码购买及二次开发
    asp.net core mvc上传大文件解决方案
    asp.net core mvc发布后显示异常错误信息的方法
    基于.net core 2.0+mysql+AceAdmin搭建一套快速开发框架
    改造kindeditor支持asp.net core mvc上传文件
    Centos7 Nginx安装使用
    Centos7 守护进程supervisord 安装使用
    Centos7 .net core 2.0安装使用
    Centos7 Mysql安装
    Centos7 Redis安装
  • 原文地址:https://www.cnblogs.com/mrlayfolk/p/13027545.html
Copyright © 2020-2023  润新知