进程间通信之匿名管道通信

概念

  • 管道是Unix中最古老的进程间通信的形式。
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
    比如:eg: ls | wc -l ps –u wbm01|grep “aa”

  • 管道的本质====》固定大小的内核缓冲区

  • 管道限制
    • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
    • 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

匿名管道pipe

  • 包含头文件<unistd.h>
  • 功能:创建一无名管道
  • 原型
    int pipe(int fd[2]);
  • 参数
    fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
  • 返回值:成功返回0,失败返回错误代码

管道创建后示意图

这里写图片描述

这里写图片描述

管道的读写规则

  • 当没有数据可读时
    • O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
    • O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
  • 当管道满的时候
    • O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
    • O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
  • 如果所有管道写端对应的文件描述符被关闭,则read返回0
  • 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE
  • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
  • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

示例代码

  • 默认阻塞读和SIGPIPE写
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)


int main(void )
{
    int pipefd[2];
    pid_t pid;

    //创建管道文件,并打开
    if (pipe(pipefd) == -1 )    
    {
        printf("pipe() err..\n");   
        return -1;
    }
    pid = fork();
    if (pid == -1)
    {
        printf("fork err..\n");
        return -1;
    }

    if (pid == 0)
    {
        close(pipefd[0]);
        write(pipefd[1], "hello hello....", 6);
        close(pipefd[1]);
        printf("child .....quit\n");
      exit(0);
    } 
    else if (pid > 0 )
    {
        int len = 0; 
        char buf[100] = {0};
        close(pipefd[1]);
        len = read(pipefd[0], buf, 100);
        printf("len:%d, buf:%s \n", len , buf);

        close(pipefd[0]);
    }

    wait(NULL);
    printf("parent ..quit\n");
    return 0;

}
  • 使用fcntl修改为非阻塞模式
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)


//非阻塞场景
int main(void )
{
    int ret = 0;
    int pipefd[2];

    ret = pipe(pipefd);
    if (ret < 0)
    {
        perror("ddd");
        exit(0);
    }
    int pid; 
    pid = fork();
    if (pid == -1)
    {
        perror("dd");
        exit(0);
    }

    if (pid == 0) //子进程
    {
        close(pipefd[0]);
        sleep(5);
        write(pipefd[1], "childaaaaaaaaa", 6 );
        printf("chile quit\n");
        close(pipefd[1]);
        exit(0);
    }

    close(pipefd[1]);
    char readbuf[1000] = {0};
    int n = 0;

    int flags = fcntl(pipefd[0], F_GETFL);
    flags = flags | O_NONBLOCK;
  ret = fcntl(pipefd[0], F_SETFL, flags);
  if (ret == -1)
  {
    printf("fcntl err.\n");
    exit(0);
  }

    n = read(pipefd[0], readbuf,sizeof(readbuf));
    if (n < 0)
    {
        printf("read no block--return right now\n");
    }
    printf("%s\n", readbuf);
    close(pipefd[0]);

    wait(NULL);

    printf("parent quit\n");

  return 0;
}
  • 所有的写端都关闭–read返回0,并非错误
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)


int main(void )
{
    int ret = 0;
    int pipefd[2];

    ret = pipe(pipefd);
    if (ret < 0)
    {
        perror("ddd");
        exit(0);
    }
    int pid; 
    pid = fork();
    if (pid == -1)
    {
        perror("dd");
        exit(0);
    }

    if (pid == 0) //子进程
    {
        close(pipefd[0]);//关闭子进程的读端

        close(pipefd[1]);//关闭子进程的写端
        exit(0);
    }

    close(pipefd[1]);关闭父进程的写端
    char readbuf[1000] = {0};
    int n = 0;
    sleep(3);//保证子进程先结束--确保所有的写端都关闭

    n = read(pipefd[0], readbuf,sizeof(readbuf));
    printf("all write port closed,num of read:%d \n", n);

    printf("%s\n", readbuf);
    close(pipefd[0]);

    wait(NULL);

    printf("parent quit\n");
  return 0;
}
  • 两端的读都被关闭,写操作会发生一个错误信号 SIGPIPE 管道断开的错误信号
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

void myhandle(int sig)
{
    printf("recv sig:%d \n", sig);
}
//演示管道两端的读都被关闭,写操作会发生一个错误信号 SIGPIPE 管道断开的错误信号
int main(void )
{
    int pipefd[2];
    pid_t pid;

    //注册管道处理函数
    signal(SIGPIPE, myhandle);

    if (pipe(pipefd) == -1 )    
    {
        printf("pipe() err..\n");   
        return -1;
    }
    pid = fork();
    if (pid == -1)
    {
        printf("fork err..\n");
        return -1;
    }

    if (pid == 0)
    {
        close(pipefd[0]);  //关闭子进程读端
        printf("child .....quit\n");
        exit(0);
    } 
    else if (pid > 0 )
    {
        int len = 0; 

        close(pipefd[0]); //关闭父进程读端

        //父进程sleep 1s后,再读
        sleep(1);

        len = write(pipefd[1], "wangbaomingwangbaoming", 6); 
        //会有信号发生(异步通知事件发生)言外之意主程序可以随时被打断
        //有了信号,就可以支持这种机制
        if (len == -1)
        {
            printf("两端的读都关闭,会发送信号,然后再执行这句话 :%d \n", len);
        }
    }

    wait(NULL);//避免僵尸进程
    printf("parent ..quit\n");
    return 0;

}
  • 测试管道容量
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
//测试管道容量

#define ERR_EXIT(m) \
    do \
    { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)


void myhandle(int sig)
{
    printf("recv sig:%d \n", sig);
}


int main(void )
{
    int pipefd[2];
    pid_t pid;

    //注册管道处理函数
    signal(SIGPIPE, myhandle);

    if (pipe(pipefd) == -1 )    
    {
        printf("pipe() err..\n");   
        return -1;
    }

    pid = fork();
    if (pid == -1)
    {
        printf("fork err..\n");
        return -1;
    }

    if (pid == 0)
    {
        int count = 0;
        int ret = 0;
        close(pipefd[0]);


        //写端变成非阻塞
        int flags = fcntl(pipefd[1], F_GETFL);
        flags = flags | O_NONBLOCK;
        ret = fcntl(pipefd[1], F_SETFL, flags);
        if (ret == -1)
        {
            printf("fcntl err.\n");
            exit(0);
        }

        while(1)
        {
            ret = write(pipefd[1] , "a", 1);    //,非阻塞模式下:写满的时候再继续写入会立即返回-1
            if (ret == -1)
            {
                perror("write pipe");
                break;
            }
            count ++;   
        }

        printf("count:%d \n\n", count);
        close(pipefd[1]);
        exit(0);
    } 
    else if (pid > 0 )
    {

        sleep(4);
        close(pipefd[0]);
        close(pipefd[1]);
    }

    wait(NULL);
    printf("parent ..quit\n");
    return 0;
}
  • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>


#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

#define TEST_SIZE 68*1024   //68K


int main(void)
{
    char a[TEST_SIZE];
    char b[TEST_SIZE];

    memset(a, 'A', sizeof(a));
    memset(b, 'B', sizeof(b));

    int pipefd[2];

    int ret = pipe(pipefd);
    if (ret == -1)
        ERR_EXIT("pipe error");

    pid_t pid;
    pid = fork();
    if (pid == 0) //A子进程写68K数据A
    {
        close(pipefd[0]);
        ret = write(pipefd[1], a, sizeof(a));
        printf("apid=%d write %d bytes to pipe\n", getpid(), ret);
        exit(0);
    }

    pid = fork();


    if (pid == 0) //B子进程写68K数据B
    {
        close(pipefd[0]);
        ret = write(pipefd[1], b, sizeof(b));
        printf("bpid=%d write %d bytes to pipe\n", getpid(), ret);
        exit(0);
    }


    close(pipefd[1]);

    sleep(1);
    int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    char buf[1024*4] = {0};
    int n = 1;
    while (1)
    {
        //父进程4k 4k的读数据,发现AB进程是交叉的写数据到管道。
        //多个进程往管道里面,写数据。
        ret = read(pipefd[0], buf, sizeof(buf)); 
        if (ret == 0)
            break;
        printf("n=%02d pid=%d read %d bytes from pipe buf[4095]=%c\n", n++, getpid(), ret, buf[4095]);
        write(fd, buf, ret);

    }
    return 0;   
}
  • 管道概念再深入–模仿ls | wc -w
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h>


#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

#define TEST_SIZE 68*1024   //68K


int main(void )
{
    int pipefd[2];
    pid_t pid;
    if (pipe(pipefd) == -1 )    
    {
        printf("pipe() err..\n");   
        return -1;
    }
    pid = fork();
    if (pid == -1)
    {
        printf("fork err..\n");
        return -1;
    }
    if (pid == 0)
    {
        close(pipefd[0]);
        //复制文件描述符pipefd[1],给标准输出,言外之意:execlp的ls命令输出到管道中
        dup2(pipefd[1], STDOUT_FILENO);
        close(pipefd[1]);

        execlp("ls", "ls", NULL);
        //如果替换新的进程印象失败,则会执行下面一句话    
        fputs("execute the cmd ls err..\n",stderr);
        exit(0);        
    } 
    else if (pid > 0 )
    {
        int len = 0; 
        char buf[100] = {0};
        close(pipefd[1]);
        //复制文件描述符pipefd[0],给标准输入,言外之意:execlp的wc命令从管道中读
        dup2(pipefd[0], STDIN_FILENO);
        close(pipefd[0]);

        execlp("wc", "wc", "-w", NULL);
        printf("len:%d, buf:%s \n", len , buf);
    }

  wait(NULL);
    printf("parent ..quit\n");
    return 0;

}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值