2.13 匿名管道通信案例 2.14管道的读写特点和管道设置为非阻塞

文章介绍了如何使用匿名管道进行父子进程间的通信。首先,通过pipe()创建管道,然后fork()创建子进程。子进程重定向其标准输出到管道的写端,并执行psaux命令。父进程关闭管道写端,从读端读取数据并过滤输出。文章还讨论了管道的读写特点,包括阻塞和非阻塞情况,并展示了如何设置管道为非阻塞模式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2.13 匿名管道通信案例

匿名管道的使用

在这里插入图片描述
在这里插入图片描述
一般匿名管道不推荐父进程与子进程交叉读写数据,一般父进程写数据子进程读数据或子进程写数据父进程读数据。

实现 ps aux | grep xxx 父子进程间通信

/*
    实现 ps aux | grep xxx 父子进程间通信
    
    子进程: 执行ps aux查看进程, 子进程结束后,将数据发送给父进程
    父进程:获取到数据,过滤
    pipe()
    execlp():执行ps aux,默认将数据发送到当前终端
    子进程将标准输出 stdout_fileno 重定向到管道的写端。  dup2函数实现文件描述符的重定向
*/

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wait.h>

int main() {

    // 重点:先创建一个管道
    int fd[2];//传出参数
    int ret = pipe(fd);

    if(ret == -1) {
        perror("pipe");
        exit(0);
    }

    // 创建子进程
    pid_t pid = fork();

    if(pid > 0) {
        // 父进程
        // 关闭写端
        close(fd[1]);
        // 从管道中读取
        char buf[1024] = {0};

        int len = -1;
        //循环读:ps aux的内容相当多
        while((len = read(fd[0], buf, sizeof(buf) - 1)) > 0) {//减一原因:有字符串的结束符
            // 过滤数据输出
            printf("%s", buf);
            memset(buf, 0, 1024);//清空数组内容
        }

        //回收子进程的内容
        wait(NULL);

    } else if(pid == 0) {
        // 子进程
        // 关闭读端
        close(fd[0]);

        // 文件描述符的重定向 stdout_fileno -> fd[1]
        dup2(fd[1], STDOUT_FILENO);
        // 执行 ps aux
        execlp("ps", "ps", "aux", NULL);
        perror("execlp");
        exit(0);
    } else {
        perror("fork");
        exit(0);
    }


    return 0;
}

2.14管道的读写特点和管道设置为非阻塞

管道的读写特点:

使用管道时,需要注意以下几种特殊的情况(假设都是阻塞I/O操作)
1.所有的指向管道写端的文件描述符都关闭了(管道写端引用计数为0),有进程从管道的读端读数据,那么管道中剩余的数据被读取以后,再次read会返回0,就像读到文件末尾一样。(read读到数据返回读到数据的字节数,read读取数据失败错误返回-1)

2.如果有指向管道写端的文件描述符没有关闭(管道的写端引用计数大于0),而持有管道写端的进程也没有往管道中写数据,这个时候有进程从管道中读取数据,那么管道中剩余的数据被读取后,再次read会阻塞,直到管道中有数据可以读了才读取数据并返回。

3.如果所有指向管道读端的文件描述符都关闭了(管道的读端引用计数为0),这个时候有进程向管道中写数据,那么该进程会收到一个信号SIGPIPE(管道破裂), 通常会导致进程异常终止。

4.如果有指向管道读端的文件描述符没有关闭(管道的读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道中写数据,那么在管道被写满的时候再次write会阻塞,直到管道中有空位置才能再次写入数据并返回。

总结:

读管道:
    管道中有数据,read返回实际读到的字节数。
    管道中无数据:
        写端被全部关闭,read返回0(相当于读到文件的末尾)
        写端没有完全关闭,read阻塞等待

写管道:
    管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号)
    管道读端没有全部关闭:
        管道已满,write阻塞
        管道没有满,write将数据写入,并返回实际写入的字节数

设置管道非阻塞

  • int flags = fcntl(fd[0], F_GETFL); // 获取原来的flag
  • flags |= O_NONBLOCK; // 修改flag的值
  • fcntl(fd[0], F_SETFL, flags); // 设置新的flag
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
/*
    设置管道非阻塞
    int flags = fcntl(fd[0], F_GETFL);  // 获取原来的flag
    flags |= O_NONBLOCK;            // 修改flag的值
    fcntl(fd[0], F_SETFL, flags);   // 设置新的flag
*/
int main() {

    // 在fork之前创建管道
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret == -1) {
        perror("pipe");
        exit(0);
    }

    // 创建子进程
    pid_t pid = fork();
    if(pid > 0) {
        // 父进程
        printf("i am parent process, pid : %d\n", getpid());

        // 关闭写端
        close(pipefd[1]);
        
        // 从管道的读取端读取数据
        char buf[1024] = {0};

        int flags = fcntl(pipefd[0], F_GETFL);  // 获取原来的flag
        flags |= O_NONBLOCK;            // 修改flag的值
        fcntl(pipefd[0], F_SETFL, flags);   // 设置新的flag

        while(1) {
            int len = read(pipefd[0], buf, sizeof(buf));
            printf("len : %d\n", len);
            printf("parent recv : %s, pid : %d\n", buf, getpid());
            memset(buf, 0, 1024);
            sleep(1);
        }

    } else if(pid == 0){
        // 子进程
        printf("i am child process, pid : %d\n", getpid());
        // 关闭读端
        close(pipefd[0]);
        char buf[1024] = {0};
        while(1) {
            // 向管道中写入数据
            char * str = "hello,i am child";
            write(pipefd[1], str, strlen(str));
            sleep(5);
        }
        
    }
    return 0;
}

tips:
VScode有关注释的快捷键
//注释:CTRL+K+C
取消//注释:CTRL+K+U
多行注释:ALT+SHITF+A
取消多行注释:ALT+SHITF+A

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值