今天我们来讲讲管道之一的匿名管道,话不多说,我们正式开始啦
1.什么是管道
管道是Unix中最古⽼的进程间通信的形式,我们把从⼀个进程连接到另⼀个进程的⼀个数据流称为⼀个"管道"

在最开始的时候,其实顶尖大佬并不打算创建一个新的东西来进行管道通信,而是打算采用现有的东西来解决进程间通信的问题,于是他们就想到了文件,对于父子进程来说,父子进程有同样的struct file,来指向同一个被打开的文件,而对于一个文件来说,他有自己的缓冲区,是不是可以让父进程将内容写入文件缓冲区,让子进程通过fd来找到这个文件,并且读取缓冲区文件内容,反之亦然,从而实现了进程间的通信,这就是管道的雏形,并且由此衍生出了匿名管道与命名管道
2.匿名管道
1.原理

看上图,父进程由*files指向files_struct,也就是文件描述符表,现在我们打开一个文件,fd = 3,对于打开的文件,该文件内容被存放在array[fd]中,我们创建一个子进程,子进程继承父进程的*files,files_struct等等,此时父子进程的指针指向同一块地方,也就是被打开的文件,于是父子进程同时可以访问该文件的缓冲区,或许你会疑问,被打开文件最后不是还要写回磁盘吗,其实真正的管道是由OS创建的一块内存,不需要刷回磁盘,和磁盘没关系,这也符合通信间的本质!!

所以真实的底层差不多是上图的样子,父进程使用pipe函数生成内存级通道,创建的子进程也可以通过fd指向此通道,此时关闭父进程的fd3与子进程的fd4,这就是进程之间的通信--单向通信,而pipe过程不需要文件路径,只需要一个pipefd[2]数组,没有文件名,所以此过程被称为匿名通信

2.pipe函数

通过man 2 pipe可以查看该系统函数

成功创建通道返回0,反之-1
对于成功创建的管道,pipefd[2]应该保存fd可读fd可写
3.pipe函数简单运用
cpp
#include <iostream>
#include <unistd.h>
int main()
{
int pfd[2] = { 0 };
int n = pipe(pfd);
if (n == -1)
{
std::cerr << "pipe error!" << std::endl;
return 1;
}
std::cout << "pfd[0] : " << pfd[0] << std::endl;
std::cout << "pfd[1] : " << pfd[1] << std::endl;
return 0;
}
运行结果如下:

为什么是3与4呢,因为fd = 0,1,2已经被标准输入输出错误占据了
3.实现一个简易的父子进程管道通信
cpp
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <cstring>
void ChildWrite(int wfd)
{
char buffer[1024];
int cnt = 0;
while (true)
{
buffer[0] = '\0';
snprintf(buffer, sizeof(buffer), "我是子进程, pid : %d, cnt : %d", getpid(), cnt++);
write(wfd, buffer, strlen(buffer));
sleep(1);
}
}
void FutherRead(int rfd)
{
char buffer[1024];
int cnt = 0;
while (true)
{
buffer[0] = '\0';
ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
buffer[n] = '\0';
std::cout << buffer << std::endl;
}
}
}
int main()
{
// 创建管道
int pfd[2] = {0};
int n = pipe(pfd);
if (n == -1)
{
std::cerr << "pipe error!" << std::endl;
return 1;
}
std::cout << "pfd[0] : " << pfd[0] << std::endl;
std::cout << "pfd[1] : " << pfd[1] << std::endl;
// 一般来说,pfd[0] -> 读端 pfd[1] -> 写端
// 创建子进程
pid_t id = fork();
if (id == 0)
{
// 关闭子进程读端
close(pfd[0]);
// 子进程向管道写入内容
ChildWrite(pfd[1]);
// 关闭子进程写端
close(pfd[1]);
}
// 关闭父进程写端
close(pfd[1]);
// 父进程读取管道内容
FutherRead(pfd[0]);
// 等待子进程结束
waitpid(id, nullptr, 0);
// 关闭父进程读端
close(pfd[0]);
return 0;
}
运行结果如下:
