💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 🍂 🌿 🍄🍝 🍛 🍤
📃个人主页 :阿然成长日记 👈点击可跳转
📆 个人专栏: 🔹数据结构与算法🔹C语言进阶🔹C++🔹Liunx
🚩 不能则学,不知则问,耻于问人,决无长进
🍭 🍯 🍎 🍏 🍊 🍋 🍒 🍇 🍉 🍓 🍑 🍈 🍌 🍐 🍍
文章目录
- 一、什么是管道?
- 二、管道的分类
- 1. 匿名管道
- 2. 命名管道
- 三、匿名管道
- 1、什么是匿名管道
- 2、匿名管道的原理
- 3、总体原理图
- 4.pipe()指令详解
- 5.匿名管道的使用
- 6.匿名管道的特点
- 7.管道运行的几种情况
- 四、命名管道
- 1、什么是命名管道
- 2、命名管道原理
- 命名管道的使用过程
- mkfifo()详解
一、什么是管道?
管道(Pipe)
是一种进程间通信机制,用于在相关进程之间传输数据。它是一种特殊的文件描述符,它可以连接一个进程的输出(写入端)到另一个进程的输入(读取端),从而使得这两个进程可以通过管道进行数据传输。
也就是说管道是单向传输的!现实生活中,我们所看听到的天然气管道、石油管道基本上都是单向传输的.
————————————————
历史上出现了许多通信机制,但是目前主流的进程通信只剩下了一小部分,管道之所以保留至今,是应为它没有多余的代码,而是巧妙的利用父子进程之间的特性,共用同一个文件进行信息的传输。
二、管道的分类
1. 匿名管道
匿名管道(Anonymous Pipe)是一种基于内存
的管道,没有与文件系统中的任何文件相关联。它是通过pipe()系统调用创建的,通常只能用于在具有亲缘关系的进程之间传递数据。匿名管道只能在创建它的进程及其子进程之间使用,无法在其他进程之间共享。
2. 命名管道
命名管道(Named Pipe,也称FIFO)是一种基于文件系统
的管道,它是通过文件系统中的特殊文件
来实现的。命名管道有一个文件名和文件系统中的其他文件一样,可以被多个进程打开和使用,用于在不同的进程之间传递数据。使用命名管道需要调用mkfifo()
函数来创建一个特殊的文件,然后打开这个文件并通过读写文件来传递数据。
命名管道通常用于需要在不同进程之间传递数据的场景,例如多进程并发编程、客户端-服务器架构、管道通信等。
三、匿名管道
1、什么是匿名管道
匿名管道(Anonymous Pipe)是进程间通信的一种机制,用于在具有亲缘关系(例如父子进程)或共享同一终端的兄弟进程之间传输数据。
-
匿名管道是一种
单向
的数据流通道,它可以用于在进程之间传递数据。通常,一个进程作为管道的写入端
(称为管道写入端),将数据写入管道;另一个进程作为管道的读取端
(称为管道读取端),从管道中读取数据。- 匿名管道的创建是通过系统调用
pipe()
来完成的。
- 匿名管道的创建是通过系统调用
2、匿名管道的原理
管道通信的背后是进程之间通过管道进行通信。
我们知道一个进程要运行,首先要加载到内存,然后创建一个task_struct结构体,里面会有一个files_struct结构体,然后这个结构体里又有一个fd_array[]数组,每个元素指向对应的文件struct_file,里面包含了文件内容等。如下图: 👇
此时我们fork之后的子进程会重新创建一份task_struct,内容继承父进程的,此时fd_array[]里的内容也被子进程继承,即父进程打开的文件 子进程也继承了下来。它们指向的文件是 相同的.
假设父进程3号文件描述符是读取文件的,4号文件描述符也用来写入文件的,子进程继承以后,fd=3也是用来读取文件的,fd=4也是用来写入文件的
此时我们想让父进程进行写入fd=4,子进程进行读取fd=3,所以父进程就要关闭读端fd=3,子进程关闭写端fd=4。
这样我们就做到了让不同的进程看到了同一份资源(通过fork子进程),而且通过文件描述符的方式完成了进程间的单向通信。
综上,管道内部本质大体是如下流程:
1️⃣ 父进程分别以读写方式打开一个文件
2️⃣ fork()创建子进程
3️⃣双方各自关闭不需要的文件描述符
3、总体原理图
4.pipe()指令详解
头文件:<unistd.h>
格式:int pipe(int pipefd[2])
- pipefd[2]是一个输出型参数。 读写文件描述符,0:代表读, 1:代表写
- 返回值:成功返回0,出错返回-1
- pipe会创建一个内存级别的文件,它没有文件名,只能通过文件描述符来找,所以又称为“
匿名文件(管道)
”
5.匿名管道的使用
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main()
{
// 1.创建管道
int pipefd[2] = {0}; // pipefd[0] :读端, pipefd[1] :写端
int n = pipe(pipefd);
assert(n != -1);
#ifdef DEBUG
#endif
// cout << pipefd[0] << " " << pipefd[1] << endl;
// 2.创建子进程
pid_t id = fork();
assert(id != -1);
if (id == 0)
{
// 子进程
// 3.构建单向通信的信道,父进程写入,子进程读取
// 3.1关闭子进程不需要的fd
close(pipefd[1]);
char buffer[1024];
while (true)
{
ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);
if (s > 0)
{
buffer[s] = 0;
cout << "child get a message [" << getpid() << "] Father# " << buffer << endl;
}
}
exit(0);
}
// 父进程
// 构建单向通信的信道
// 3.1 关闭父进程不需要的fd
close(pipefd[0]);
string message = "我是父进程,我正在给你发消息";
int count = 0;
char send_buffer[1024];
while (true)
{
// 3.2 构建一个变化的字符串
snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d", message.c_str(), getpid(), count++);
// 3.3 写入
write(pipefd[1], send_buffer, strlen(send_buffer));
// 3.4 sleep
sleep(1);
}
pid_t ret = waitpid(id, nullptr, 0);
assert(ret > 0);
close(pipefd[1]);
return 0;
}
6.匿名管道的特点
1️⃣ 管道是用来进行具有血缘关系的进程进行进程间通信 — 常用于父子间
通信
2️⃣ 管道具有通过让进程间协同,提供了访问控制
3️⃣ 管道提供的是面向字节流式的通信服务 — 面向字节流
— 通过定制协议实现
4️⃣ 管道是基于文件的,文件的生命周期是随进程的,即管道的生命周期也随进程
的!
5️⃣ 管道是单向通信
的,就是半双工通信的一种特殊方式.
7.管道运行的几种情况
写快,读慢 | 写满就不能再写了 |
---|---|
写慢,读快 | 管道没有数据时,读必须等待 |
上面两种这是由访问控制提供的.
写关,读继续读 | 会标识读到了文件结尾 |
---|---|
写继续写,读关 | OS会终止写进程 |
四、命名管道
与匿名管道不同,命名管道不需要亲缘关系的进程之间,也不需要共享同一终端。任意进程可以通过打开命名管道的读取端和写入端来与其进行通信。
1、什么是命名管道
命名管道通过在文件系统中创建一个特殊的文件
来实现通信。这个特殊的文件被称为FIFO
(First-in, First-out)或命名管道
。用于在无关的进程之间进行数据传输。
2、命名管道原理
和匿名管道一样,想让双方通信,必须先让双方看到同一份资源!它和匿名管道本质是一样的,只是看到资源的方式不同。
匿名管道是通过父子进程继承来看到同一份资源的,也叫做管道文件,这个文件是纯内存级
的,所以没有名字,叫做匿名管道。
命名管道是在磁盘上有一个特殊的文件
,这个文件可以被打开,但是打开后不会将内存中的数据刷新到磁盘。在磁盘上就有了路径,而路径是唯一的,所以双方就可以通过文件的路径
来看到同一份资源,即管道文件。
命名管道的使用过程
1️⃣ 创建命名管道:通过调用系统调用 mkfifo() 在文件系统中创建一个特殊的文件,这个文件就是命名管道。创建命名管道时,需要指定管道的名称和所需的权限。
2️⃣ 打开命名管道:进程通过调用系统调用 open() 来打开命名管道,得到一个文件描述符。进程可以通过打开具有相同名称的文件来打开命名管道的读取端和写入端。
3️⃣ 进程通信:一旦命名管道被打开,进程就可以使用文件描述符进行通信。每个进程可以选择读取端或写入端与命名管道进行交互。
4️⃣ 数据传输:进程在读取端通过调用 read() 系统调用从命名管道中读取数据,而在写入端通过调用 write() 系统调用将数据写入命名管道中。读取端和写入端可以通过文件描述符进行数据的发送和接收。
5️⃣ 关闭命名管道:进程完成通信后,可以通过调用 close() 关闭命名管道的文件描述符来释放资源。当所有对命名管道的引用都被关闭时,管道的文件系统条目将被删除。
mkfifo()详解
mkfifo [选项] 文件名
在程序中执行时创建int mkfifo(const char *filename,mode_t mode);
作用:创建上面提到的特殊文件----命名管道