管道通信
前言:
介绍管道知识之前我们先要引入一个概念-----进程通信
🍊进程通信目的:
- 👋 数据传输:一个进程需要将它的数据发送给另一个进程
- 🤚资源共享:多个进程之间共享同样的资源。
- 🖐通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们) 发生了某种事件(如进程终止时要通知父进程)。
- ✋进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程)此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变.
🍎 进程间通信的发展
- 👌 管道
- 🤌 System V进程间通信
- 🤏 POSIX进程间通信
🍐 进程间通信分类
- ✌️管道
- 匿名管道pipe
- 命名管道
- 🤞System V
- System V IPC
- System V 消息队列
- System V 共享内存
- System V 信号量
- 🤟POSIX IPC
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
1.管道介绍
🍋背景介绍:
- 进程之间具有独立性,需要多进程之间协调来完成一件事,涉及到数据交互,成本会非常的高,比较繁琐,因为进程具有独立性
- 进程独立不是完全独立,在特殊场景之下要进行数据交互
🍌什么是管道
- 👈管道是Unix中最古老的进程间通信的形式。
- 👉 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
管道是单项通信的,有进口有出口!
🍉匿名管道
这里引用其他博主的文章做介绍,仅供学习,侵删-
原文链接
🍓匿名管道管理
1.父进程创建管道,对同一文件分别以读&写方式打开
2. 父进程fork创建子进程
3. 因为管道是一个只能单向通信的信道,父子进程需要关闭对应读写端,至于谁关闭谁,取决于通信方向。
- 🖕于是,通过子进程继承父进程资源的特性,双方进程看到了同一份资源
🍇匿名管道创建
- pipe谁调用就让以读写方式打开一个文件(内存级文件)
#include <unistd.h>
int pipe(int pipefd[2]);
- 参数pipefd:输出型参数!通过这个参数拿到两个打开的fd
- 返回值:成功返回0;失败返回-1
数组pipefd用于返回两个指向管道读端和写端的文件描述符:
- 👇下面按照之前讲的原理进行逐一操作:①创建管道 ②父进程创建子进程 ③关闭对应的读写端,形成单向信道
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <string.h>
#include <assert.h>
using namespace std;
int main()
{
//1.创建管道
int pipefd[2] = {0};
int n = pipe(pipefd); //失败返回-1
assert(n != -1); //只在debug下有效
(void)n; //仅此证明n被使用过
#ifdef DEBUG
cout<< "pipefd[0]" << pipefd[0] << endl; //3
cout<< "pipefd[1]" << pipefd[1] << endl; //4
#endif
//2.创建子进程
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
//子进程
//3. 构建单向通信的信道
//3.1 子进程关闭写端[1]
close(pipefd[1]);
exit(0);
}
//父进程
//父进程关闭读端[0]
close(pipefd[0]);
return 0;
}
在此基础上,我们就要进行通信了,实际上就是对某个文件进行写入,因为管道也是文件,下面提提前查看要用到的函数
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
返回值:
- 返回写入的字节数
- 零表示未写入任何内容,这里意味着对端进程关闭文件描述符
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
- 测试代码
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <string.h>
#include <assert.h>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
int main()
{
//1.创建管道
int pipefd[2] = {0};
int n = pipe(pipefd); //失败返回-1
assert(n != -1); //只在debug下有效
(void)n; //仅此证明n被使用过
#ifdef DEBUG
cout<< "pipefd[0]" << pipefd[0] << endl; //3
cout<< "pipefd[1]" << pipefd[1] << endl; //4
#endif
//2.创建子进程
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
//子进程 - 读
//3. 构建单向通信的信道
//3.1 子进程关闭写端[1]
close(pipefd[1]);
char buffer[1024];
while(1)
{
size_t s = read(pipefd[0], buffer, sizeof(buffer)-1);
if(s > 0)
{
buffer[s] = 0;//因为read是系统调用,没有/0,此处给加上
cout<<"child get a message["<< getpid() << "] 爸爸对你说" << buffer << endl;
}
}
//close(pipefd[0]);
exit(0);
}
//父进程 - 写
//父进程关闭读端[0]
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);
count++;
//3.3写入
write(pipefd[1], send_buffer, strlen(send_buffer));//此处strlen不能+1
//3.4 故意sleep
sleep(1);
}
pid_t ret = waitpid(id, nullptr, 0);
assert(ret != -1);
(void)ret;
return 0;
}
🍈匿名管道注意事项
为什么不定义一个全局的buffer来进行通信呢?
- 因为有写时拷贝的存在,无法更改通信!上面的方法就是把数据交给管道,让对方通过管道进行读取
😎匿名管道通信的4种情况
之前父子进程同时向显示器中写入的时候,二者会互斥 —— 缺乏访问控制
而对于管道进行读取的时候,父进程如果写的慢,子进程就会等待读取 —— 这就是说明管道具有访问控制(是自带的)
✨读阻塞:写快,读慢
父进程疯狂的进行写入,子进程隔10秒才读取,子进程会把这10秒内父进程写入的所有数据都一次性的打印出来!
代码如非就是在父进程添加了打印conut,子进程sleep(10),可以自行的在test代码上添加
父进程写了1220次,子进程一次就给你读完了,读写之间没有关系,这就叫做流式的服务。
也就是管道是面向字节流的,也就是只有字节的概念,究竟读成什么样也无法保证,甚至可能读出乱码,所以父子进程通信也是需要制定协议的,但这个我们网络再细说。。
✨写阻塞:写慢,读快
管道没有数据的时候,读端必须等待:父进程每隔2秒才进行写入,子进程疯狂的读取
✨写端关闭
父进程写入10秒,后把写端fd关闭,读端会怎么样?
写入的一方,fd没有关闭,如果有数据就读,没有数据就等
写入的一方,fd关闭了,读取的一方,read会返回0,表示读到了文件结尾,退出读端
✨写端关闭
父进程写入10秒,后把写端fd关闭,读端会怎么样?
写入的一方,fd没有关闭,如果有数据就读,没有数据就等
写入的一方,fd关闭了,读取的一方,read会返回0,表示读到了文件结尾,退出读端
由此可知,当发生情况四时,操作系统向子进程发送的是SIGPIPE信号将子进程终止的。
🐋总结上述的4中场景:
- 写快,读慢,写满了不能再写了
- 写慢,读快,管道没有数据的时候,读端必须等待
- 写关,读取的一方,read会返回0,表示读到了文件结尾,退出读端
- 读关,写继续写,OS终止写进程 ——
🧐由上总结出匿名管道的5个特点 ——
- 管道是一个单向通信的通信管道,是半双工通信的一种特殊情况
- 管道是用来进行具有血缘关系的进程进行进程间通信 —— 常用于父子通信
- 管道具有通过让进程间协同,提供了访问控制!
- 管道是 面向字节流 —— 协议(后面详谈)
- 管道是基于文件的,管道的声明周期是随进程的
😎管道的大小
管道的容量是有限的,如果管道已满,那么写端将阻塞或失败,那么管道的最大容量是多少呢?
- man手册查询
🍒 命名管道
为了解决匿名管道只能在父子之间通信,我们引入命名管道,可以在任意不相关进程进行通信
多个进程打开同一个文件,OS只会创建一个struct_file
命名管道就是一种特殊类型的文件(可以被打开,但不会将数据刷新进磁盘),两个进程通过命名管道的文件名打开同一个管道文件,此时这两个进程也就看到了同一份资源,进而就可以进行通信了。
命名管道就是通过唯一路径/文件名的方式定位唯一磁盘文件的
ps:命名管道和匿名管道一样,都是内存文件,只不过命名管道在磁盘有一个简单的映像(所以有名字),但这个映像的大小永远为0,因为命名管道和匿名管道都不会将通信数据刷新到磁盘当中。
🎨创建命名管道
- 💛 make FIFOs 在命令行上创建命名管道
mkfifo (named pipes)
- 🍑FIFO:First In First Out 队列呀
点击跳转:参考博客