Linux--详解管道、匿名管道pipe命名管道fifo(进程通信、代码+解析)

管道通信

  • 前言:
    • 进程通信目的:
    • 进程间通信的发展
    • 进程间通信分类
  • 1.管道介绍
    • 背景介绍:
    • 什么是管道
    • 匿名管道
    • 匿名管道管理
    • 匿名管道创建
    • 匿名管道注意事项
      • 匿名管道通信的4种情况
      • ✨读阻塞:写快,读慢
      • ✨写阻塞:写慢,读快
      • ✨写端关闭
      • ✨写端关闭
      • 总结上述的4中场景:
      • 由上总结出匿名管道的5个特点 ——
      • 管道的大小
  • 命名管道
    • 创建命名管道

前言:

介绍管道知识之前我们先要引入一个概念-----进程通信

进程通信目的:

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们) 发生了某种事件(如进程终止时要通知父进程)。
  • ✋进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程)此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变.

进程间通信的发展

  • 管道
  • System V进程间通信
  • POSIX进程间通信

进程间通信分类

  • ✌️管道
  1. 匿名管道pipe
  2. 命名管道
  • System V
  1. System V IPC
  2. System V 消息队列
  3. System V 共享内存
  4. System V 信号量
  • POSIX IPC
  1. 消息队列
  2. 共享内存
  3. 信号量
  4. 互斥量
  5. 条件变量
  6. 读写锁

1.管道介绍

背景介绍:

  1. 进程之间具有独立性,需要多进程之间协调来完成一件事,涉及到数据交互,成本会非常的高,比较繁琐,因为进程具有独立性
  2. 进程独立不是完全独立,在特殊场景之下要进行数据交互

什么是管道

  • 管道是Unix中最古老的进程间通信的形式。
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
    Linux--详解管道、匿名管道pipe命名管道fifo(进程通信、代码+解析)_第1张图片
    管道是单项通信的,有进口有出口!

匿名管道

这里引用其他博主的文章做介绍,仅供学习,侵删-
原文链接

Linux--详解管道、匿名管道pipe命名管道fifo(进程通信、代码+解析)_第2张图片

匿名管道管理

1.父进程创建管道,对同一文件分别以读&写方式打开
Linux--详解管道、匿名管道pipe命名管道fifo(进程通信、代码+解析)_第3张图片
2. 父进程fork创建子进程
Linux--详解管道、匿名管道pipe命名管道fifo(进程通信、代码+解析)_第4张图片
3. 因为管道是一个只能单向通信的信道,父子进程需要关闭对应读写端,至于谁关闭谁,取决于通信方向。
Linux--详解管道、匿名管道pipe命名管道fifo(进程通信、代码+解析)_第5张图片

  • 于是,通过子进程继承父进程资源的特性,双方进程看到了同一份资源

匿名管道创建

  • pipe谁调用就让以读写方式打开一个文件(内存级文件)
#include 
int pipe(int pipefd[2]);

  • 参数pipefd:输出型参数!通过这个参数拿到两个打开的fd
  • 返回值:成功返回0;失败返回-1
    数组pipefd用于返回两个指向管道读端和写端的文件描述符:
    4
  • 下面按照之前讲的原理进行逐一操作:①创建管道 ②父进程创建子进程 ③关闭对应的读写端,形成单向信道
#include 
#include 
#include 
#include 
#include 
#include 

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 
ssize_t write(int fd, const void *buf, size_t count);

#include 
ssize_t read(int fd, void *buf, size_t count);
返回值:
- 返回写入的字节数
- 零表示未写入任何内容,这里意味着对端进程关闭文件描述符

#include 
unsigned int sleep(unsigned int seconds);

  • 测试代码
#include 
#include 
#include 
#include 
#include 
#include 
#include
#include

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代码上添加
Linux--详解管道、匿名管道pipe命名管道fifo(进程通信、代码+解析)_第6张图片
父进程写了1220次,子进程一次就给你读完了,读写之间没有关系,这就叫做流式的服务。
也就是管道是面向字节流的,也就是只有字节的概念,究竟读成什么样也无法保证,甚至可能读出乱码,所以父子进程通信也是需要制定协议的,但这个我们网络再细说。。

✨写阻塞:写慢,读快

管道没有数据的时候,读端必须等待:父进程每隔2秒才进行写入,子进程疯狂的读取

✨写端关闭

父进程写入10秒,后把写端fd关闭,读端会怎么样?

写入的一方,fd没有关闭,如果有数据就读,没有数据就等
写入的一方,fd关闭了,读取的一方,read会返回0,表示读到了文件结尾,退出读端

✨写端关闭

父进程写入10秒,后把写端fd关闭,读端会怎么样?

写入的一方,fd没有关闭,如果有数据就读,没有数据就等
写入的一方,fd关闭了,读取的一方,read会返回0,表示读到了文件结尾,退出读端

由此可知,当发生情况四时,操作系统向子进程发送的是SIGPIPE信号将子进程终止的。

总结上述的4中场景:

  • 写快,读慢,写满了不能再写了
  1. 写慢,读快,管道没有数据的时候,读端必须等待
  2. 写关,读取的一方,read会返回0,表示读到了文件结尾,退出读端
  3. 读关,写继续写,OS终止写进程 ——

由上总结出匿名管道的5个特点 ——

  1. 管道是一个单向通信的通信管道,是半双工通信的一种特殊情况
  2. 管道是用来进行具有血缘关系的进程进行进程间通信 —— 常用于父子通信
  3. 管道具有通过让进程间协同,提供了访问控制!
  4. 管道是 面向字节流 —— 协议(后面详谈)
  5. 管道是基于文件的,管道的声明周期是随进程的

管道的大小

管道的容量是有限的,如果管道已满,那么写端将阻塞或失败,那么管道的最大容量是多少呢?

  • man手册查询

Linux--详解管道、匿名管道pipe命名管道fifo(进程通信、代码+解析)_第7张图片

命名管道

为了解决匿名管道只能在父子之间通信,我们引入命名管道,可以在任意不相关进程进行通信

多个进程打开同一个文件,OS只会创建一个struct_file
Linux--详解管道、匿名管道pipe命名管道fifo(进程通信、代码+解析)_第8张图片
命名管道就是一种特殊类型的文件(可以被打开,但不会将数据刷新进磁盘),两个进程通过命名管道的文件名打开同一个管道文件,此时这两个进程也就看到了同一份资源,进而就可以进行通信了。

命名管道就是通过唯一路径/文件名的方式定位唯一磁盘文件的

ps:命名管道和匿名管道一样,都是内存文件,只不过命名管道在磁盘有一个简单的映像(所以有名字),但这个映像的大小永远为0,因为命名管道和匿名管道都不会将通信数据刷新到磁盘当中。

创建命名管道

  • make FIFOs 在命令行上创建命名管道
mkfifo (named pipes)

  • FIFO:First In First Out 队列呀
    Linux--详解管道、匿名管道pipe命名管道fifo(进程通信、代码+解析)_第9张图片
    点击跳转:参考博客

你可能感兴趣的:(Linux,linux,运维)