【Linux 15】进程间通信的方式 - 管道

news2024/9/20 21:47:19

文章目录

  • 🌈 一、管道介绍
  • 🌈 二、匿名管道
    • ⭐ 1. 匿名管道的概念
    • ⭐ 2. 匿名管道的创建
    • ⭐ 3. 匿名管道的本质
    • ⭐ 4. 匿名管道的使用
    • ⭐ 5. 匿名管道的特点
    • ⭐ 6. 匿名管道的大小
  • 🌈 三、命名管道
    • ⭐ 1. 命名管道的概念
    • ⭐ 2. 命名管道的创建
    • ⭐ 3. 命名管道的使用
    • ⭐ 4. 命名管道的特点
  • 🌈 四、管道的特殊情况

🌈 一、管道介绍

什么是管道

  • 管道是一个进程连接到另一个进程的一个数据流

举个例子

  • 现在有个统计云服务器用户登录个数的指令:who | wc -l
    • who 用于查看当前云服务器的登录用户 (一行一个用户),wc -l 用于统计当前的行数。
  • who 和 wc -l 是两个不同的进程,它们却能很好的配合得出想要的结果,靠的就是管道 |

在这里插入图片描述
在这里插入图片描述

🌈 二、匿名管道

⭐ 1. 匿名管道的概念

  • 匿名管道不需要名字,只用保证具有亲缘关系的进程能够看到即可,常用于父子进程之间的通信
  • 创建子进程时,子进程会拷贝父进程的 PCB、地址空间、页表、文件描述符表 (struct files_struct)。
    • 子进程在拷贝时用的是浅拷贝,因此父子进程的文件描述符表中的 fd_array 数组中的指针指向的都是同一份文件资源,这才导致父子进程能看到同一份文件。
  • 由于进程间通信的本质是让多个进程看到同一份资源,此时父子进程看到同一个文件及缓冲区不就属于进程间通信了吗。

在这里插入图片描述

⭐ 2. 匿名管道的创建

1. 创建匿名管道函数

#include <unistd.h>

int pipe(int pipefd[2]);
  • 功能:创建一条无名管道。
  • 参数:pipefd 是一个文件描述符数组且是个输出型参数,pipefd [0] 表示管道的读端,pipefd [1] 表示管道的写端。
  • 返回:创建匿名管道成功返回 0,失败则返回 - 1 并设置错误码。

2. 创建匿名管道实例

#include <cassert>
#include <unistd.h>
#include <iostream>

using std::cin;
using std::cout;
using std::endl;

int main()
{   
    // 创建管道
    int pipefd[2] = { 0 };
    int n = pipe(pipefd);   
    assert(0 == n);

    cout << "读端 fd: " << pipefd[0] << endl;
    cout << "写端 fd: " << pipefd[1] << endl;

    return 0;
}

在这里插入图片描述

⭐ 3. 匿名管道的本质

1. 读写端的本质

  • 读端和写端说白了就是两个不同 file 结构体,它们指向同一个文件管道文件
  • 读端的 file 结构体负责从管道文件中读消息,写端的 file 结构体负责往管道文件写消息。

2. 实现单向通信的过程

  1. 父进程向 OS 申请创建管道,设置管道的读写端。

在这里插入图片描述

  1. 父进程 fork 出子进程,由于子进程会对父进程进行拷贝,子进程也会持有管道的读写端。

在这里插入图片描述

  1. 保证管道单向通信的特性,父子进程不能同时持有管道的读写端。
    • 父进程向子进程发送数据:需要父进程关闭读端 pipefd[0],子进程关闭写端 pipefd[1]。
    • 子进程向父进程发送数据:需要父进程关闭写端 pipefd[1],子进程关闭读端 pipefd[0]。

在这里插入图片描述
在这里插入图片描述

⭐ 4. 匿名管道的使用

  • 实现一个子进程向父进程发送数据的单向通信管道。

1. 代码展示

#include <cstring>
#include <cassert>
#include <unistd.h>
#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>

using std::cin;
using std::cout;
using std::endl;

int main()
{
    /* 1.创建管道 */
    int pipefd[2] = {0};
    int n = pipe(pipefd);
    assert(0 == n);

    /* 2.创建子进程 */
    pid_t id = fork();
    if (id < 0) // 子进程创建失败
        return 1;

    /* 3.建立子写,父读的单向通信的管道 */
    // 父进程关闭写端,子进程关闭读端
    if (0 == id)
    {
    	// 子进程关闭读端
        close(pipefd[0]); 

        // 子进程从写端向管道写入消息
        for (int count = 10; count >= 0; count--)
        {
            char message[1024];
            snprintf(message, sizeof(message) - 1,
                     "hello father, I am child, pid: %d, count: %d",
                     getpid(), count);
            write(pipefd[1], message, strlen(message));
            sleep(1);
        }
		
		// 子进程退出
        exit(0); 
    }

    /* 4.父进程从读端从管道读取消息并打印 */
    close(pipefd[1]); // 父进程关闭写端
    char buffer[1024];
    while (true)
    {
        ssize_t n = read(pipefd[0], buffer, sizeof(buffer) - 1);
        if (n > 0) 		// 读取成功
        {
            buffer[n] = '\0';
            cout << "child say: " << buffer << " to me!" << endl;
        }
        else if (0 == n)// 读取结束
        {
            break;
        }
    }

    pid_t rid = waitpid(id, nullptr, 0); // 父进程等待子进程退出
    if (rid == id)                       // 等待成功
        cout << "wait success" << endl;

    return 0;
}

2. 结果展示

在这里插入图片描述

⭐ 5. 匿名管道的特点

  • 匿名管道有如下 5 种特性。

1. 匿名管道只能用于具有共同祖先的进程

  • 匿名管道只能用于具有亲缘关系的进程之间进行通信,常用于父子进程之间的通信。

2. 匿名管道提供的是流式服务

  • 流式概念:提供一个通信的信道,写端就负责写,读端就负责读。但是,具体写多少、读多少完全由上层决定。底层就只提供一个数据通信的信道。它不关心数据本身的一些细节或格式,这叫做面向字节流。
  • 流式服务:数据没有明确的分割,一次拿多少数据都行。

3. 匿名管道内部自带同步与互斥机制

  • 同步:父子进程在执行时,具有一定的顺序性。
  • 互斥:父子进程不管谁是写端,谁是读端,同时只能有一个访问管道文件。

4. 匿名管道的生命周期随进程结束而结束

  • 管道本质上是通过文件进行通信的,也就是说管道依赖于文件系统。
  • 当所有打开该文件的进程都退出后,该文件也就会被释放掉,所以说匿名管道的生命周期随进程的结束而结束。

5. 匿名管道采用半双工的通信方式

  • 匿名管道是一个只允许单向通信的共享资源文件。
    • 即一个管道只允许进程 A 写进程 B 读,或进程 B 写进程 A 读。
  • 如果想实现双向通信,只需要建立两个匿名管道即可。
    • 即管道 1 用于进程 A 写进程 B 读,管道 2 用于进程 B 写进程 A 读。

在这里插入图片描述

⭐ 6. 匿名管道的大小

  • 管道文件的容量也是有极限的,如果管道已满,那么写端将阻塞或写失败,可以通过以下 2 种方式获取管道的大小。

1. 编写代码验证

  • 现在已经知道了写端在管道满时会阻塞等待,可以根据这个特性获取管道文件的大小。
    • 实现方式:定义一个值为 0 的计数器,写端每次往管道种写入 1 个字节,然后让计数器 +1,直到写端阻塞住时的计数器的值就是管道的大小。
#include <cassert>
#include <unistd.h>
#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>

using std::cout;
using std::endl;

int main()
{
    /* 1.创建管道 */
    int pipefd[2] = {0};
    int n = pipe(pipefd);
    assert(0 == n);

    /* 2.创建子进程 */
    pid_t id = fork();

    /* 3.只让子进程不停的向管道写入 */
    if (0 == id)
    {
    	// 子进程关闭读端
        close(pipefd[0]); 

        // 子进程不停的从写端向管道写入消息
        int count = 0;
        while (true)
        {
            char ch = 'a';
            write(pipefd[1], &ch, 1);   // 每次向管道中写入 1 个字节的内容
            cout << "write ......: " << ++count << endl;
        }

        exit(0); // 子进程退出
    }

    /* 4.父进程停止从管道种读取数据 */
    close(pipefd[1]); 					 // 父进程关闭写端
    pid_t rid = waitpid(id, nullptr, 0); // 父进程等待子进程退出
    if (rid == id)                       // 等待成功
        cout << "wait success" << endl;

    return 0;
}

在这里插入图片描述

2. 使用 ulimit -a 指令查看

在这里插入图片描述

🌈 三、命名管道

⭐ 1. 命名管道的概念

  • 匿名管道的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
  • 如果想实现多个不相关的进程之间的通信,可以使用命名管道
  • 命名管道是一种特殊类型的文件 (FIFO 文件),命名管道有自己的路径 + 文件名。
  • 进程通过命名管道唯一名字 (路径 + 文件名) 打开同一个管道文件,让多个进程看到同一份资源,即可实现进程间通信。

⭐ 2. 命名管道的创建

  • 创建命名管道的方式有如下 2 种。

1. 通过 mkfifo 指令创建命名管道

  • 可以在命令行中输入如下指令创建一个命名管道,命名管道的名字可自定义。
mkfifo filename	

在这里插入图片描述

2. 通过 mkfifo 函数创建命名管道

#include <sys/stat.h>
#include <sys/types.h>

int mkfifo(const char* pathname, mode_t mode);
  • 参数:
    • pathname:如果该参数是个路径,则在指定路径下创建命名管道;如果只是个文件名,则在当前路径下创建命名管道。
    • mode:创建命名管道时,要赋予该命名管道文件的默认权限。
  • 返回:如果创建命名管道成功则返回 0,失败则返回 -1。
#include <cassert>
#include <sys/stat.h>
#include <sys/types.h>

const char* pathname = "name_pipe";

int main()
{
    // 在当前路径创建名为 name_pipe 的命名管道并赋予其 0666 的权限
    int n = mkfifo(pathname, 0666);
    assert(0 == n);

    return 0;
}

在这里插入图片描述

⭐ 3. 命名管道的使用

  • 命名管道有如下 2 种使用方式。

1. 通过指令使用命名管道

  • 使用输出重定向的方式往命名管道中写入文件。
    • 如:使用 echo “hello pipe” > name_pipe 向之前创建的命名管道写入数据。
  • 使用输入重定向的方式从命名管道中读取文件。
    • 如:使用 cat < name_pipe 从命名管道中读取数据,然后打印到屏幕上。

在这里插入图片描述

2. 通过代码使用命名管道

  • 由于命名管道就是个管道文件,那么只要一个进程以读方式打开管道文件,另一个进程以写方式打开管道文件,即可实现进程之间的互相通信。
    • 例:实现一个两个进程打开同一份命名管道,客户端进程负责向管道写入,服务端进程负责从管道读取。
  1. server.cpp :创建命名管道,以读方式打开命名管道,作为读端从命名管道中读取数据并打印。
#include <fcntl.h>
#include <cassert>
#include <unistd.h>
#include <iostream>
#include <sys/stat.h>
#include <sys/types.h>

using std::cout;
using std::endl;

#define FILENAME "fifo" // 命名管道的名字

int main()
{
    // 创建命名管道
    int n = mkfifo(FILENAME, 0666);
    assert(0 == n);

    // 以只方式打开命名管道文件
    int rfd = open(FILENAME, O_RDONLY); 
    assert(rfd >= 0);

    // 从管道中读取数据
    char buffer[1024];
    while (true)
    {
        ssize_t s = read(rfd, buffer, sizeof(buffer) - 1);
        if (s > 0)       // 读取成功
        {
            buffer[s] = '\0';
            cout << "client say: " << buffer << endl;
        }
        else if (0 == s) // 写端关闭
        {
            break;
        }
    }

    // 关闭读端
    close(rfd);

    return 0;
}
  1. client.cpp :以写方式打开命名管道,作为写端获取用户输入并向命名管道中写入数据。
#include <string>
#include <fcntl.h>
#include <cassert>
#include <unistd.h>
#include <iostream>
#include <sys/stat.h>
#include <sys/types.h>

using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::getline;

#define FILENAME "fifo" // 命名管道的名字

int main()
{
    // 以写方式打开命名管道文件
    int wfd = open(FILENAME, O_WRONLY);
    assert(wfd >= 0);

    // 获取用户输入并将其写入管道
    string message;
    while (true)
    {   
        cout << "请输入: ";
        getline(cin, message);
        ssize_t s = write(wfd, message.c_str(),  message.size());
        assert(s >= 0);
    }
    
    // 关闭写端
    close(wfd);

    return 0;
}
  • 结果展示:

在这里插入图片描述

⭐ 4. 命名管道的特点

1. 任何进程都可以使用命名管道

  • 命名管道不受进程间亲缘关系的限制,任何进程都可以通过管道的名称来访问管道进行通信

2. 匿名管道提供的是流式服务

  • 流式概念:提供一个通信的信道,写端就负责写,读端就负责读。但是,具体写多少、读多少完全由上层决定。底层就只提供一个数据通信的信道。它不关心数据本身的一些细节或格式,这叫做面向字节流。
  • 流式服务:数据没有明确的分割,一次拿多少数据都行。

3. 匿名管道内部自带同步与互斥机制

  • 同步:父子进程在执行时,具有一定的顺序性。
  • 互斥:父子进程不管谁是写端,谁是读端,同时只能有一个访问管道文件。

4. 命名管道不随进程的终止而消失

  • 命名管道是有全局唯一的名称 (路径 + 文件名),其是作为文件系统中的特殊文件存在的。
  • 即使创建它的进程终止,只要该管道文件未被删除,其他进程依然可以通过命名管道的名字来使用它。

5. 命名管道提供半/全双工通信

  • 命名管道可以是单向通信,也可以是双向通信,具体取决于管道的打开方式和通信协议。

🌈 四、管道的特殊情况

  1. 正常情况下,如果管道文件没数据了,读端必须等待,直到有数据 (写端写入数据) 了为止 。
  2. 正常情况下,如果管道文件被写满了,写端必须等待,直到有空间 (读端读出数据) 了为止 。
  3. 写端关闭,读端一直读取:读端会读到 read 的返回值为 0,表示读到了文件的结尾。
  4. 读端关闭,写端一直写入:OS 不会做任何浪费时空的事情,会直接向目标进程发送 SIGPIPE (13 号) 信号将写端进程终止。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1940225.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

USB转多路UART - USB 基础

一、 前言 断断续续做了不少USB相关开发&#xff0c;但是没有系统去了解过&#xff0c;遇到问题就很被动了。做这个USB转UART的项目就是&#xff0c;于是专门花了一天的时间学习USB及CDC相关&#xff0c;到写这文章时估计也忘得差不多了&#xff0c;趁项目收尾阶段记录一下&am…

OLAP引擎之Kylin

Apache Kylin 是一个开源的分布式分析引擎&#xff0c;设计用于在大数据环境中实现极快的在线分析处理 (OLAP) 查询。它主要用于解决大数据分析中的性能问题&#xff0c;并为大规模数据提供交互式的查询体验。Kylin 是由 eBay 于 2014 年开源的&#xff0c;现由 Apache 基金会管…

某直聘每日算法变更分析

带大家分析一下每日算法的变更情况如何破解&#xff1a; 1. 找到算法起始点 前面都是字符串的拼接&#xff0c;可以不用管&#xff0c;重点我们看数组的操作 af0d473b.js:11950 S: 191 e: 3 : af0d473b.js:11326 gl: 1 G: 1 : af0d473b.js:11950 S: 60 e: 3 : af0d473b.js:113…

Java 写一个可以持续发送消息的socket服务端

前言 最近在学习flink, 为了模仿一个持续的无界的数据源, 所以需要一个可以持续发送消息的socket服务端. 先上效果图 效果图 socket服务端可以持续的发送消息, flink端是一个统计单词出现总数的消费端,效果图如下 源代码 flink的消费端就不展示了, 需要引入一些依赖和版本…

多态性概念 OOPS

大家好&#xff01;今天&#xff0c;我们将探讨面向对象编程 (OOP) 中的一个基本概念 - 多态性。具体来说&#xff0c;我们将重点介绍其三种主要形式&#xff1a;方法重载、方法覆盖和方法隐藏。对于任何使用 OOP 语言&#xff08;例如 C#&#xff09;的程序员来说&#xff0c;…

嵌入式人工智能(17-基于树莓派4B的电机控制-伺服电机SG90)

伺服电机主要适用于角度需要不断变化且可以保持的控制系统&#xff0c;常见的机械臂、多足机器人、遥 控船、摄像头云台等都可以使用伺服电机来实现。 1、简介 伺服电动机又被称为执行电动机、舵机&#xff0c;如图9.4所示&#xff0c;是由直流电机、减速齿轮组、电位器和控制…

C语言 | Leetcode C语言题解之第264题丑数II

题目&#xff1a; 题解&#xff1a; int nthUglyNumber(int n) {int dp[n 1];dp[1] 1;int p2 1, p3 1, p5 1;for (int i 2; i < n; i) {int num2 dp[p2] * 2, num3 dp[p3] * 3, num5 dp[p5] * 5;dp[i] fmin(fmin(num2, num3), num5);if (dp[i] num2) {p2;}if (d…

Fastgpt接入ChatTTS本地AI语音合成模型实现语音实时朗读

前言 FastGPT 默认使用了 OpenAI 的 LLM 模型和语音合成模型,如果想要私有化部署的话,可以使用开源TTS项目f封装成兼容open ai的协议兼容的API接口。参考文章 《ChatTTS-一款适用于日常对话的AI生成式语音模型》 FastGPT接入本地AI语音TTS Base Url为你部署本地语音识别web…

Shell程序设计

各位看官&#xff0c;从今天开始&#xff0c;我们进入新的专栏Shell学习&#xff0c;Shell 是操作系统的命令行界面&#xff0c;它允许用户通过输入命令与操作系统交互。常见的 Shell 有 Bash 和 Zsh&#xff0c;它们可以执行用户输入的命令或运行脚本文件。Shell 广泛应用于系…

Google AI加速代码迁移

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

知名的GPU算力租用平台,适合进行大规模深度学习训练

目录 引言 GPU算力租用的重要性 当前市场趋势 选择GPU算力平台的关键因素 知名的GPU算力租用平台&#xff0c;适合进行大规模深度学习训练 引言 在数字时代&#xff0c;计算能力是推动科技创新的核心驱动力&#xff0c;尤其是在人工智能&#xff08;AI&#xff09;、机器学…

【ARM】SMMU系统虚拟化整理

目录 1.MMU的基本介绍 1.1 特点梳理 2.功能 DVM interface PTW interface 2.1 操作流程 2.1.1 StreamID 2.1.2 安全状态&#xff1a; 2.1.3 HUM 2.1.4 可配置的操作特性 Outstanding transactions per TBU QoS 仲裁 2.2 Cache结构 2.2.1 Micro TLB 2.2.2 Macro…

交叉编译ethtool(ubuntu 2018)

参考文章&#xff1a;https://www.cnblogs.com/nazhen/p/16800427.html https://blog.csdn.net/weixin_43128044/article/details/137953913 1、下载相关安装包 //ethtool依赖libmul git clone http://git.netfilter.org/libmnl //ethtool源码 git clone http://git.kernel.or…

go语言Gin框架的学习路线(七)

GORM入门(基于七米老师) 目录 GORM入门 安装 连接数据库 连接MySQL 连接PostgreSQL 连接Sqlite3 连接SQL Server 我们搞一个连接MySQL的例子 创建数据库 GORM操作MySQL GORM是一个流行的Go语言ORM&#xff08;对象关系映射&#xff09;库&#xff0c;它提供了一种方…

Golang | Leetcode Golang题解之第273题整数转换英文表示

题目&#xff1a; 题解&#xff1a; var (singles []string{"", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine"}teens []string{&…

高效日志管理系统设计

设计一个高效的日志管理系统&#xff0c;旨在确保日志数据能够被有效收集、存储、分析和检索&#xff0c;同时也要考虑到系统的可扩展性、可靠性和易用性。以下是高效日志管理系统设计的关键要素&#xff1a; 1. 日志收集 自动收集&#xff1a;使用轻量级的日志采集代理&…

windows11 vmware安装记录

注意&#xff1a;windows11不要安装vmware16版本以下的&#xff01;&#xff01;&#xff01;会报错&#xff0c;与内核冲突&#xff0c;只有关闭内核才可以运动&#xff0c;但是这样电脑的安全性得不到保障。 Windows11 中 Vmware Workstations16 安装CentOS 7_windows featu…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 小区小朋友统计(100分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,支持题目在线…

二染色,CF 1594D - The Number of Imposters

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 1594D - The Number of Imposters 二、解题报告 1、思路分析 并查集&…

某手某聚星登录算法分析

KS某聚星登录算法分析 某手某聚星登录算法分析第一步&#xff1a;抓包-登录第二步&#xff1a;定位加密入口第三步&#xff1a;分析加密算法第四步&#xff1a;算法实现 某手某聚星登录算法分析 在这篇文章中&#xff0c;我们将详细解析某手某聚星的登录算法&#xff0c;涵盖从…