【Linux】进程间通信 -- 管道

对于进程间通信的理解

首先,进程间通信的本质是,让不同的进程看到同一份资源(这份资源不能隶属于任何一个进程,即应该是共享的)。而进程间通信的目的是为了实现多进程之间的协同。
但由于进程运行具有独立性(虚拟地址空间+页表 保证了进程运行的独立性),所以要实现进程间的通行难度会比较大。
管道通信作为进程间通信的一种方式,Linux原生就能提供。其通信方式又分为两种:匿名管道 和 命名管道。

匿名管道

匿名管道通信常用于父子进程间的通信。通过fork创建子进程,让具有血缘关系的进程能够进行通信。
其实现通信的步骤主要有3步:

  1. 父进程分别以读和写方式打开同一个文件
  2. fork()创建子进程
  3. 父子进程各自关闭自己不需要的文件描述符

在这里插入图片描述
如上图看管道本质还是文件。
既然管道通信,首先要能够创建出管道。pipe系统接口可以帮助创建管道。其参数pipefd是一个数组,

// pipefd[0]对应读端的文件描述符
// pipefd[1]对应写端的文件描述符
int pipe(int pipefd[2]);
// 匿名管道通信测试
void Test1()
{
    // 1.创建管道
    int pipefd[2] = {0};
    int ret = pipe(pipefd);
    if(ret != 0)
    {
        perror("pipe");
        exit(1);
    }
    // 测试打开的文件描述符
    cout << "pipefd[0]: " << pipefd[0] << endl;
    cout << "pipefd[1]: " << pipefd[1] << endl;

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

    if(pid > 0)
    {
    	// 3.构建单向通行的信道,父进程写入,子进程读取
        // 3.1.父进程 -- 写
        
        // 关闭读端
        close(pipefd[0]);
        int count = 0;
        while(true)
        {
        	// 不断写如变化的信息
            string msg = "hello world" + to_string(count++);
            write(pipefd[1], msg.c_str(), msg.size());
            sleep(1);

            if(count > 5)
            {
                cout << "write quit" << endl;
                break;
            }
        }
        // 关闭写端
        close(pipefd[1]);
		
		// 4.等待子进程
        pid_t wpid = waitpid(pid, nullptr, 0);
        if(wpid == -1)
        {
            perror("waitpid");
            exit(3);
        }
    }
    else if(pid == 0)
    {
        // 3.2.子进程 -- 读
		
		// 关闭写端
        close(pipefd[1]);
		
		// 不断读取信息
        char receive[128] = {0};
        while(true)
        {
            ssize_t size = read(pipefd[0], receive, 127);
            if(size > 0)
            {
                cout << "receive: " << receive << endl;
            }
            else if(size == 0) 
            {
                cout << "write quit, read quit" << endl;
                break;
            }
            else
            {
                perror("read");
                exit(4);
            }
        }
        // 关闭读端
        close(pipefd[0]);
    }
    else 
    {
        perror("fork");
        exit(2);
    }
}

在这里插入图片描述
通过匿名管道我们还可以模拟进程池的设计。

// 简单的进程池设计
#define PROCESS_NUM 5

using f = function<void()>;
unordered_map<int, f> task;

void load()
{
    task[1] = [](){cout << "sub process[" << getpid() << "]->void Task1()" << endl;};
    task[2] = [](){cout << "sub process[" << getpid() << "]->void Task2()" << endl;};
    task[3] = [](){cout << "sub process[" << getpid() << "]->void Task3()" << endl;};
    task[4] = [](){cout << "sub process[" << getpid() << "]->void Task4()" << endl;};
}

void sendTask(int fd, pid_t pid, int task_num)
{
    write(fd, &task_num, sizeof(task_num));
    cout << "process[" << pid << "] execute " << "task" << task_num << " by " << fd << endl;
}

int waitTask(int fd)
{
    int task_num = 0;
    ssize_t size = read(fd, &task_num, sizeof(task_num));
    if(size == 0)
    {
        return 0;
    }
    if(size == sizeof(task_num))
    {
        return task_num;
    }
    return -1;
}

void Test2()
{
    load();
    vector<pair<int, pid_t>> process;
    // 创建多个进程
    for(int i = 0; i < PROCESS_NUM; ++i)
    {
        // 创建管道
        int pipefd[2] = {0};
        int ret = pipe(pipefd);
        if(ret != 0)
        {
            perror("pipe");
            exit(1);
        }

        // 创建子进程
        pid_t pid = fork();

        if(pid == 0)
        {
            // 子进程 -- 读
            close(pipefd[1]);

            while(true)
            {
                // 等待任务
                int task_num = waitTask(pipefd[0]);
                if(task_num == 0)
                {
                    break;
                }
                else if(task_num >= 1 && task_num <= task.size())
                {
                    task[task_num]();
                }
                else
                {
                    perror("waitTask");
                    exit(3);
                }
            }
            exit(0);
        }
        else if (pid < 0)
        {
            perror("fork");
            exit(2);
        }

        // 父进程读端关闭
        close(pipefd[0]);
        process.emplace_back(pipefd[1], pid);
    }

    // 父进程 -- 写
    srand((unsigned int)time(0));
    
    while(true)
    {
        // 选择一个进程 -- 随机数方式的负载均衡
        int process_num = rand() % process.size();
        // 选择一个任务
        // int task_num = rand() % task.size() + 1;
        int task_num = 0;
        cout << "please enter your task num: ";
        cin >> task_num;

        // 派发任务
        sendTask(process[process_num].first, process[process_num].second, task_num);
    }

    // 关闭fd
    for(const auto& e : process)
    {
        close(e.first);
    }

    // 回收子进程
    for(const auto& e : process)
    {
        waitpid(e.second, nullptr, 0);
    }
}

在这里插入图片描述

命名管道

可以用mkfifo命令创建一个命名管道。如下图是一个命名管道的小实验。
在这里插入图片描述
也可以通过mkfifo接口进行命名管道文件的创建。
在这里插入图片描述
命名管道通信的测试。

// 1. log.hpp
#include <iostream>

enum ErrLevel
{
    lev_0,
    lev_1,
    lev_2,
    lev_3,
    lev_4
};

const std::string error[] = {
    "err_0",
    "err_1",
    "err_2",
    "err_3",
    "err_4"
};

std::ostream& Log(const std::string& msg, int level)
{
    std::cout << " | " << (unsigned int)time(0) << " | " << error[level] << " | " << msg << " |";
    return std::cout;
}

// 2. comm.hpp
#include <sys/types.h>
#include <sys/stat.h>
#include <wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>

using namespace std;

#include "log.hpp"

#define MODE 0666
#define SIZE 128

// 命名管道,通过文件路径,让不同进程能看到这同一份资源
string named_pipe_path = "/home/zs/linux/testcpp/fifo.ipc";

// 3. server.cpp
static void getMsg(int fd)
{
    char buffer[SIZE];
    while(true)
    {
        memset(buffer, '\0', sizeof(buffer));
        ssize_t size = read(fd, buffer, sizeof(buffer) - 1); // ssize_t - long int
        if(size > 0)
        {
            cout << "[" << getpid() << "]" << "client say:" << buffer << endl;
        }
        else if(size == 0)
        {
            cerr << "[" << getpid() << "]" << "read end of file, client quit, then server quit" << endl;
            break;
        }
        else
        {
            perror("read");
            break;
        }
    }
}

void test()
{
    // 1.创建管道文件
    if(0 != mkfifo(named_pipe_path.c_str(), MODE))
    {
        perror("mkfifo");
        exit(1);
    }
    Log("创建管道文件成功", lev_0) << endl;

    // 2.文件操作
    int fd = open(named_pipe_path.c_str(), O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        exit(2);
    }
    Log("打开管道文件成功", lev_0) << endl;

    for(int i = 0; i < 3; ++i)
    {
        pid_t pid = fork();
        if(pid == 0)
        {
            // 3.通信
            getMsg(fd);
            exit(0);
        }
    }

    for(int i = 0; i < 3; ++i)
    {
        waitpid(-1, nullptr, 0);
    }

    // 4.关闭文件
    close(fd);
    Log("关闭管道文件成功", lev_0) << endl;
    unlink(named_pipe_path.c_str()); // 通信完毕,删除管道文件
    Log("删除管道文件成功", lev_0) << endl;
}

// 4. client.cpp
void test()
{
    // 1.获取管道文件
    int fd = open(named_pipe_path.c_str(), O_WRONLY);
    if(fd < 0)
    {
        perror("open");
        exit(1);
    }
    
    // 2.通信
    string message;
    while(true)
    {
        cout << "please enter your message: ";
        getline(cin, message);
        write(fd, message.c_str(), message.size());
    }

    // 关闭
    close(fd);
}

在这里插入图片描述

管道通信总结

  1. 管道常用来进行具有血缘关系的进程间的通信
  2. 管道让进程间协同,提供了访问控制
  3. 管道提供的是面向流式的通信服务
  4. 管道是基于文件的,文件的生命周期跟随进程,管道的生命周期也跟随进程
  5. 管道用于单向通信,属于半双工通信的一种特殊情况

管道本质是文件,又和传统的文件又不一样。管道文件不会将数据刷新到磁盘。
匿名管道通过父子继承的方式看到同一份资源,命名管道通过文件路径的唯一性看到同一份资源,从而达到不同进程间通信的目的。
对于管道文件:
如果写的一方很快,读的一方很慢,当管道写满时,写端必须等待;
如果写的一方很慢,读的一方很快,当管道没有数据时,读端必须等待;
如果写端先被关闭了,读端会读到文件结尾;
如果读端先被关闭了,操作系统会终止写端进程。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python——第十天

今日目标&#xff1a; 常见排序和查找 常见排序和查找: 冒泡排序 选择排序 插入排序 选择排序&#xff1a; 假设"第一个值"是最小值&#xff0c;就要每一轮找到真正的最小值&#xff0c;并且和假设的这个值交换 [1, 3, 2, 10, -8, 9, -30, 7] 1、 [-30, 3, 2, 10, -8…

代码随想录算法训练营第四十八天|121. 买卖股票的最佳时机 122.买卖股票的最佳时机II

文档讲解&#xff1a;代码随想录 视频讲解&#xff1a;代码随想录B站账号 状态&#xff1a;看了视频题解和文章解析后做出来了 121. 买卖股票的最佳时机 class Solution:def maxProfit(self, prices: List[int]) -> int:if len(prices) 0:return 0dp [[0] * 2 for _ in r…

数据结构与算法【堆】的Java实现

前言 之前已经说过堆的特点了&#xff0c;具体文章在数据结构与算法【队列】的Java实现-CSDN博客。因此直接实现堆的其他功能。 建堆 所谓建堆&#xff0c;就是将一个初始的堆变为大顶堆或是小顶堆。这里以大顶堆为例。展示如何建堆。 找到最后一个非叶子节点从后向前&…

【作业】操作系统实验一:进程和线程

文章目录 实验内容一、进程的创建1、编辑源程序2、编辑结果3、编译和运行程序4、解释运行结果 二、进程共享1、运行2、解释运行结果 三、进程终止1、运行2、解释运行结果 四、进程同步1、运行2、解释运行结果 五、Linux中子进程映像的重新装入1、运行2、解释运行结果 六、线程1…

三十一、W5100S/W5500+RP2040树莓派Pico<TCP_Server多路socket>

文章目录 1 前言2 简介2. 1 使用多路socket的优点2.2 多路socket数据交互原理2.3 多路socket应用场景 3 WIZnet以太网芯片4 多路socket设置示例概述以及使用4.1 流程图4.2 准备工作核心4.3 连接方式4.4 主要代码概述4.5 结果演示 5 注意事项6 相关链接 1 前言 W5100S/W5500是一…

SQL零基础入门教程,贼拉详细!贼拉简单! 速通数据库期末考!(七)

LEFT JOIN LEFT JOIN 同样用于关联两个表&#xff0c;ON 关键字后指定两个表共有的字段作为匹配条件&#xff0c;与 INNER JOIN 不同的地方在于匹配不上的数据行&#xff0c;INNER JOIN 对两表匹配不上的数据行不返回结果&#xff0c;而 LEFT JOIN 只对右表&#xff08;table2…

gRPC 四模式之 客户端流RPC模式

客户端流RPC模式 在客户端流 RPC 模式中&#xff0c;客户端会发送多个请求给服务器端&#xff0c;而不再是单个请求。服务器端则会发送一个响应给客户端。但是&#xff0c;服务器端不一定要等到从客户端接收到所有消息后才发送响应。基于这样的逻辑&#xff0c;我们可以在接收…

基于SSM+Vue的鲜花销售系统/网上花店系统

基于SSM的鲜花销售系统/网上花店系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringMyBatisSpringMVC工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 主页 管理员界面 摘要 鲜花销售系统是一个基于SSM&#xff08;Spring …

SQL零基础入门教程,贼拉详细!贼拉简单! 速通数据库期末考!(八)

FULL OUTER JOIN 除了前面讲到的 INNER JOIN&#xff08;内连接&#xff09;、LEFT JOIN&#xff08;左连接&#xff09;、RIGHT JOIN&#xff08;右连接&#xff09;&#xff0c;还有另外一种关联方式&#xff0c;即 FULL OUTER JOIN&#xff08;全外连接&#xff09; FULL O…

深信服AC设备用户认证

拓扑图 目录 拓扑图 一. 无需认证 思路&#xff1a;创建用户和组&#xff0c;将无需认证策略和用户绑定 1.创建组&#xff0c;组里添加用户 2. 新建不需要认证策略&#xff0c;将不需要认证策略和用户关联 3.验证 二.密码认证 思路&#xff1a;创建用户和组&#xff0c;并…

通过bat脚本控制Oracle服务启动停止

1、将Oracle服务全部设置为手动启动 初始安装Oracle之后服务启动状态&#xff1a; 2、服务功能介绍 3、构建服务启动/停止bat脚本 注意&#xff1a;编码选择ANSI(如果编码不是ANSI运行脚本会显示乱码) echo off :main cls echo 当前Oracle服务状态&#xff1a; for /f &quo…

Swagger(4):Swagger配置

在上一张的项目中创建SwaggerConfig&#xff0c;进行配置文档内容。 1 配置基本信息 Docket&#xff1a;摘要对象&#xff0c;通过对象配置描述文件的信息。 apiInfo:设置描述文件中info。参数类型ApiInfo select():返回ApiSelectorBuilder对象&#xff0c;通过对象调用buil…

Java 某市教育局综合信息管理平台

1) 项目简介 “互联网智慧教育”管理平台&#xff0c;实现全市教育信息系统集中建设和教育数据在云平台的汇集&#xff0c;在全市中小学整体实现电子班牌、家校通等功能&#xff0c;选取部分重点学校进行一卡通系统试点建设&#xff0c;实现智能化门禁、道闸、实体卡等功能…

后端面经学习自测(三)

文章目录 1、ArrayList和Linkedlist区别&#xff1f;2、ArrayList扩容机制&#xff1f;3、ArrayList和Linkedlist分别能做什么场景&#xff1f;4、事务特性&#xff1f;MySQL事务Redis事务Spring事务5、在Spring中事务失效的场景&#xff1f;6、Java泛型&#xff1f;7、泛型擦除…

FPGA_边沿检测电路设计

FPGA_边沿检测电路设计 边沿检测原理图波形图分析实现方法方法一&#xff1a;与逻辑实现方法二&#xff1a;或逻辑实现方法三&#xff1a;与逻辑实现 边沿检测原理图 由状态转移表可以看出&#xff0c;其转换条件中需要检测到下降沿以及上升沿&#xff0c;而边沿检测其原理就是…

1.0 Zookeeper 教程

分类 Zookeeper 教程 ZooKeeper 是 Apache 软件基金会的一个软件项目&#xff0c;它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。 ZooKeeper 的架构通过冗余服务实现高可用性。 Zookeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来&…

团结引擎已全面支持 OpenHarmony 操作系统

Unity 中国宣布与开放原子开源基金会达成平台级战略合作。 据称团结引擎已全面支持 OpenHarmony 操作系统&#xff0c;同时将为 OpenHarmony 生态快速带来更多高品质游戏与实时 3D 内容。Unity 称现在用户可以 “在 OpenHarmony 框架中感受到与安卓和 iOS 同样丝滑的游戏体验”…

分支限界法(1)--旅行商问题

一、概述 有n个城市&#xff0c;旅行者要访问所有n个城市&#xff0c;最终回到起始点&#xff0c;假设起始点给定为1&#xff0c;城市间距离已知&#xff0c;求能够完成旅行的最短距离。题干如下图。 算法&#xff1a;分支限界法&#xff0c;使用队列进行bfs搜索。 二、代码 i…

工程化实战 - 前端AST(进阶)

###脚手架 *快速自动化搭建启动工具 目标: ####第一步:处理依赖 npm i path npm i chalk4.1.0 npm i fs-extra npm i inquirer8.2.2 npm i commander npm i axios npm i download-git-repo //ora easy-table figlet ####第二步:处理工程入口 ####3.加入命令交互 交互好帮手…

<MySQL> 如何合理的设计数据库中的表?数据表设计的三种关系

目录 一、表的设计 二、一对一关系 三、一对多关系 四、多对多关系 一、表的设计 数据库设计就是根据需要创建出符合需求的表。 首先根据需求找到体系中的关键实体对象&#xff0c;通常每个实体对象都会有一个表&#xff0c;表中包含了这个实体的相关属性。 再理清楚实体对…