Linux 多进程程序调试实例(三)--匿名管道和命名管道
升级版多进程程序调试
虽然标题是多进程进程调试实例,但是实际上由于进程调试的关键步骤在(一)、(二)中已经阐述过了,所以(三)中主要内容是进程之间的通信,本文主要是用来介绍匿名管道的使用。
匿名管道
- 思路
匿名管道只适用于具有血缘关系的父子进程,具体的实现是通过 pipe 函数
- 父进程在 fork 之前,调用 pipe 函数,生成一个大小为2可以操作的文件描述符组,数组的第一个文件描述符表示读,第二个表示写
- 管道本质是一个内核缓冲区,一端将数据写入内核中,一端从内核中读取,数据结构是一个环形队列,所以管道的数据流向是单向的,也就意味着一个管道只能完成数据从一端到另一端的单向传输,如果想要双向数据通信,需要两个管道
- 管道的读写两端是阻塞的,大小默认为4K,可修改
- 代码
// 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;
}
- 调试步骤
- 调试步骤同一般的多进程调试步骤一致
- fork 之后的子进程是继承了父进程的进程级资源,比如打开的文件描述符,所以 execl 函数执行的进程可以通过传参的方式把文件描述符传入直接使用
- 匿名管道只使用的有血缘关系的父子进程,就是基于fork出来的子进程可以继承父进程的文件描述符。
命名管道
-
介绍
- 匿名管道只能在有血缘关系的进程之间通信,命名管道则适用于没有血缘关系的进程之间通信
- 命名管道本质是一个设备文件,创建者往该文件写,其他文件可以从该文件读。这样实现进程通信的目的
-
代码
//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端读不出数据的情况。
- 介绍
- 命名管道,只要能访问到路径,就可以通信;
- 数据先进先出
- 调试
- 命名管道是两个独立的进程使用一个共享文件读写,和普通的程序调试没有区别,可以直接参考普通的程序方式进行调试
标签:
进程通信
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理