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

news2024/12/26 11:23:11

对于进程间通信的理解

首先,进程间通信的本质是,让不同的进程看到同一份资源(这份资源不能隶属于任何一个进程,即应该是共享的)。而进程间通信的目的是为了实现多进程之间的协同。
但由于进程运行具有独立性(虚拟地址空间+页表 保证了进程运行的独立性),所以要实现进程间的通行难度会比较大。
管道通信作为进程间通信的一种方式,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. 管道用于单向通信,属于半双工通信的一种特殊情况

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

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

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

相关文章

stable diffusion十七种controlnet详细使用方法总结

个人网站&#xff1a;https://tianfeng.space 前言 最近不知道发点什么&#xff0c;做个controlnet 使用方法总结好了&#xff0c;如果你们对所有controlnet用法&#xff0c;可能了解但是有点模糊&#xff0c;希望能对你们有用。 一、SD controlnet 我统一下其他参数&#…

python 对图像进行聚类分析

import cv2 import numpy as np from sklearn.cluster import KMeans import time# 中文路径读取 def cv_imread(filePath, cv2_falgcv2.COLOR_BGR2RGB): cv_img cv2.imdecode(np.fromfile(filePath, dtypenp.uint8), cv2_falg) return cv_img# 自定义装饰器计算时间 def…

解决:虚拟机远程连接失败

问题 使用FinalShell远程连接虚拟机的时候连接不上 发现 虚拟机用的VMware&#xff0c;Linux发行版是CentOs 7&#xff0c;发现在虚拟机中使用ping www.baidu.com是成功的&#xff0c;但是使用FinalShell远程连接不上虚拟机&#xff0c;本地网络也ping不通虚拟机&#xff0c…

10-19 HttpServletResponse

相应的对象 web开发模型&#xff1a;基于请求与相应的模型 一问一答的模型 Response对象:响应对象,封装服务器给客户端的相关的信息 顶级接口: ServletResponse 父接口:HttpServletResponse response对象的功能分为以下四种:(都是服务器干的事注意) 设置响应头信息; 发送状态码…

2023年【四川省安全员A证】考试资料及四川省安全员A证考试试卷

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年四川省安全员A证考试资料为正在备考四川省安全员A证操作证的学员准备的理论考试专题&#xff0c;每个月更新的四川省安全员A证考试试卷祝您顺利通过四川省安全员A证考试。 1、【多选题】《建设工程安全生产管理…

竞赛选题 疲劳驾驶检测系统 python

文章目录 0 前言1 课题背景2 Dlib人脸识别2.1 简介2.2 Dlib优点2.3 相关代码2.4 人脸数据库2.5 人脸录入加识别效果 3 疲劳检测算法3.1 眼睛检测算法3.2 打哈欠检测算法3.3 点头检测算法 4 PyQt54.1 简介4.2相关界面代码 5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#x…

GamingTcUI.dll丢失修复,最全面的GamingTcUI.dll修复指南

热衷于电脑游戏的用户可能会在启动游戏时遇到这样的错误信息&#xff1a;"无法启动应用&#xff0c;因为找不到GamingTcUI.dll"。那么这个GamingTcUI.dll文件是什么&#xff1f;如何解决这个问题呢&#xff1f;我们将在本文中进行详细讲解。 一.GamingTcUI.dll是什么…

kibana8.10.4简单使用

1.创建discovery里的日志项目 点击stack management 选择kibana里的数据视图&#xff0c;右上角创建数据视图&#xff0c;输入名称。索引范围。例子 example-* ,匹配以example-开头的所有index。 然后点击 保存数据视图到kibana&#xff0c; 2.Kibana多用户创建及角色权限控…

向pycdc项目提的一个pr

向pycdc项目提的一个pr 前言 pycdc这个项目&#xff0c;我之前一直有在关注&#xff0c;之前使用他反编译python3.10项目&#xff0c;之前使用的 uncompyle6无法反编译pyhton3.10生成的pyc文件&#xff0c;但是pycdc可以&#xff0c;但是反编译效果感觉不如uncompyle6。但是版…

Gem5模拟器学习之旅

安装gem5 模拟器 翻译自官网&#xff08;https://www.gem5.org/documentation/learning_gem5/part1/building/&#xff09; 支持的操作系统和环境 gem5的设计考虑到了Linux环境。我们定期在 Ubuntu 18.04、Ubuntu 20.04 和 Ubuntu 22.04 上进行测试&#xff0c;以确保 gem5 在…

WeTab--颜值与实力并存的浏览器插件

一.前言 现在的浏览器花花绿绿&#xff0c;有大量的广告与信息&#xff0c;令人目不暇接。有没有一款好用的浏览器插件可以解决这个问题呢&#xff1f;我愿称WeTab为版本答案。 WeTab的界面&#xff1a; 干净又整洁。最最关键的是还有智能AI供你服务。 这个WeTabAI就像chatgp…

如何去掉照片中多余路人?一分钟帮你搞定

在外出拍照时&#xff0c;可能会遇到一些不希望出现在照片中的路人&#xff0c;比如在旅游景点、公共场所或者街头拍摄时突然闯入镜头的人。这些路人的出现可能会破坏照片的整体氛围&#xff0c;影响照片的美观度。因此&#xff0c;需要使用一些方法去掉这些多余的路人&#xf…

Python编程技巧 – 对象和类

Python编程技巧 – 对象和类 Python Programming Skills – Object and Class Python是一种面向对象的高级程序语言。 本文简要介绍用Python如何实现面向对象&#xff0c;对象和类的声明及使用&#xff0c;以及面向对象的特征&#xff0c;及其如何使用属性和方法的介绍&#x…

[深度学习]卷积神经网络的概念,入门构建(代码实例)

# 不再任何人,任何组织的身上倾注任何的感情,或许这就是能活得更开心的办法 0.写在前面: 卷积神经网络的部分在之前就已经有所接触,这里重新更全面地总结一下关于深度学习中卷积神经网络的部分.并且在这里对如何构建代码,一些新的思想和网络做出一点点补充,同时会持续更新一些…

深信服AC密码认证(外部认证:LDAP认证)

拓扑图 搭建好自己AD域服务器&#xff0c;我搭建的服务器域名叫做liyanlongyu.com&#xff0c;如何搭建这里我就不做演示了 一.在AC中添加自己AD域服务器 二.添加LDAP认证策略 验证&#xff1a; 未认证发现&#xff0c;无法上网 点击网页&#xff0c;弹出认证页面 认证后&…

HAL库STM32串口开启DMA接收数据

STM32CubeMx的配置 此博客仅仅作为记录&#xff0c;这个像是有bug一样&#xff0c;有时候好使&#xff0c;有时候不好&#xff0c;所以趁现在好使赶紧记录一下&#xff0c;很多地方用到串口接收数据&#xff0c;DMA又是一种非常好的接收方式&#xff0c;可以节约CPU的时间&…

【C语言】深入理解数据表示与存储

文章目录 1.分析上述源程序中的变量在机器内是如何表示的1.1.并给出变量在内存中的存储情况&#xff08;变量占多少个字节&#xff0c;每个字节的地址和存放的数据是多少&#xff09;1.2.说明其存放顺序&#xff08;大端、小端次序&#xff1f;&#xff09;1.3.对齐方式&#x…

2023.11.18 - hadoop之zookeeper分布式协调服务

1.zookeeper简介 ZooKeeper概念: Zookeeper是一个分布式协调服务的开源框架。本质上是一个分布式的小文件存储系统 ZooKeeper作用: 主要用来解决分布式集群中应用系统的一致性问题。 ZooKeeper结构: 采用树形层次结构&#xff0c;没有目录与文件之分,ZooKeeper树中的每个节点被…

代码随想录 Day49 单调栈01 LeetCode LeetCodeT739每日温度 T496 下一个最大元素I

前言 折磨的死去活来的动态规划终于结束啦,今天秋秋给大家带来两题非常经典的单调栈问题,可能你不清楚单调栈是什么,可以用来解决什么问题,今天我们就来一步一步的逐渐了解单调栈,到能够灵活使用单调栈.注意以下讲解中&#xff0c;顺序的描述为 从栈头到栈底的顺序 什么时候用单…

“流量为王”的时代一去不返!如何押注互联网下一个黄金十年

目录 1“流量为王”的时代一去不返&#xff01;如何押注互联网下一个黄金十年 2AI夺走的第一份工作竟是OpenAI CEO&#xff1f;阿尔特曼被“扫地出门”&#xff0c;网友热评&#xff1a;是被GPT-5取代了吗&#xff1f;马斯克更“毒”&#xff0c;挂出求职申请链接 3GPT-4V新玩…