Linux 多进程程序调试实例(三)--匿名管道和命名管道

升级版多进程程序调试

虽然标题是多进程进程调试实例,但是实际上由于进程调试的关键步骤在(一)、(二)中已经阐述过了,所以(三)中主要内容是进程之间的通信,本文主要是用来介绍匿名管道的使用。

匿名管道

  1. 思路
    匿名管道只适用于具有血缘关系的父子进程,具体的实现是通过 pipe 函数
  • 父进程在 fork 之前,调用 pipe 函数,生成一个大小为2可以操作的文件描述符组,数组的第一个文件描述符表示读,第二个表示写
  • 管道本质是一个内核缓冲区,一端将数据写入内核中,一端从内核中读取,数据结构是一个环形队列,所以管道的数据流向是单向的,也就意味着一个管道只能完成数据从一端到另一端的单向传输,如果想要双向数据通信,需要两个管道
  • 管道的读写两端是阻塞的,大小默认为4K,可修改
  1. 代码
// pipe.cpp
#include<cstdio>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

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

    if(-1 == pipe(pipefd)){
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    int cpid = fork();
    if(-1 == cpid) {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    else if (0 == cpid){
        close(pipefd[1]);
        char buf[128] = "0";

        sprintf(buf, "%d", pipefd[0]);
        execl("./pipeExec","pipeExec",buf,NULL);

        // printf("I'am a child start\n");
        // while(read(pipefd[0], &buf, 1) > 0){
        //     write(STDOUT_FILENO, &buf, 1);
        // }
        // write(STDOUT_FILENO, "\n", 1);
        // close(pipefd[0]);
        // printf("I'am a child end\n");

        exit(EXIT_SUCCESS);
    }
    else{
        close(pipefd[0]);
        printf("I'am a parent start\n");
        const char *buf = "hello, I'am parent";
        write(pipefd[1], buf, strlen(buf));
        printf("I'am a parent end\n");
        close(pipefd[1]);

        wait(NULL);
        exit(EXIT_SUCCESS);
    }
}

// pipeExec.cpp
#include<iostream>
#include<cstdio>
#include<unistd.h>
#include<stdlib.h>

int main(int argc, char* argv[]){
    // std::cout << __FILE__ << " " << __func__ << " " << __LINE__ << std::endl;
    printf("I'am a child exec start\n");

    if(argc > 1) {
        for(int i = 0; i < argc; ++i){
            printf("argv[%d]: %s\n", i, argv[i]);
        }
    }

    int fd = atoi(argv[1]);
    char ch;
    while(read(fd,&ch,1)>0){
        write(STDOUT_FILENO, &ch, 1);
    }

    write(STDOUT_FILENO, "\n", 1);

    // char buf[128] = {0};
    // read(fd, buf, 50);
    // printf("%s\n",buf);

    close(fd);
    printf("I'am a child end\n");
    return 0;
}
  1. 调试步骤
  • 调试步骤同一般的多进程调试步骤一致
  • fork 之后的子进程是继承了父进程的进程级资源,比如打开的文件描述符,所以 execl 函数执行的进程可以通过传参的方式把文件描述符传入直接使用
  • 匿名管道只使用的有血缘关系的父子进程,就是基于fork出来的子进程可以继承父进程的文件描述符。

命名管道

  1. 介绍

    • 匿名管道只能在有血缘关系的进程之间通信,命名管道则适用于没有血缘关系的进程之间通信
    • 命名管道本质是一个设备文件,创建者往该文件写,其他文件可以从该文件读。这样实现进程通信的目的
  2. 代码

//server.cpp
#include<cstdio>
#include<cstring>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<time.h>
#include<errno.h>
#include<stdlib.h>   // exit

#define PIPE_NAME "file_fifo"

int main()
{
    printf("I am %d process.\n",getpid());

    int fd = 0;
    if((fd = open(PIPE_NAME,O_WRONLY)) < 0){
        perror("open FIFO error");
        exit(1);
    }

    time_t tp;
    char buf[1024] = {0};
    for(int i = 0; i < 10; ++i){
        time(&tp);
        int n = sprintf(buf, "Process %d's time is %s", getpid(), ctime(&tp));
        printf("Send message: %s",buf);
        if(write(fd, buf,n+1) < 0){
            perror("write FIFO failed");
            close(fd);
            return 0;
        }
        sleep(1);
    }

    close(fd);
    return 0;
}
//client.cpp
#include<cstdio>
#include<fcntl.h>
#include<sys/stat.h>
#include<errno.h>
#include<unistd.h>

#define PIPE_NAME "file_fifo"

int main()
{
    if(mkfifo(PIPE_NAME, 0666) < 0 && errno != EEXIST){
        perror("Create FIFO Failed");
    }

    int fd = open(PIPE_NAME, O_RDONLY);
    if(fd < 0){
        perror("Open FIFO failed");
        return -1;
    }

    int len = 0;
    char buf[1024] = {0};
    while((len = read(fd,buf,1024)) > 0){
        printf("Read Mesage: %s",buf);
    }

    close(fd);
    return 0;
}
  • 补充
    • 如果 if((fd = open(PIPE_NAME,O_WRONLY)) < 0) 中 少一个括号,会出现server 程序边读边写,clinet端读不出数据的情况。
  1. 介绍
    • 命名管道,只要能访问到路径,就可以通信;
    • 数据先进先出
  2. 调试
    • 命名管道是两个独立的进程使用一个共享文件读写,和普通的程序调试没有区别,可以直接参考普通的程序方式进行调试
posted @   王清河  阅读(160)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示