首页 > 系统相关 >进程间通信 之 管道

进程间通信 之 管道

时间:2024-03-19 22:33:51浏览次数:23  
标签:std code int 间通信 管道 进程 include

目录

什么是管道通信

管道通信的特点

匿名管道

命名管道


进程间通信的本质:让不同的进程看到同一份资源!

什么是管道通信

管道是Unix中最古老的进程间通信的形式。它是一种基于文件的通信形式,我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。

管道文件是一种纯内存文件,不需要刷新到磁盘。管道只允许单向通信,如果要双向通信的话,需要建立两个管道,“互相读写”!

管道通信的特点

管道通信时可能会遇到以下四种情况:

  • 1、正常情况,如果管道没有数据了,读端必须等待,直到有数据为止(写端写入数据)
  • 2、正常情况,如果管道被写满了,写端就必须等待,直到有空间为止(读端读走数据)
  • 3、写端关闭,读端如果一直读的话,就会导致 read 返回值为0,表示读到了文件结尾为止
  • 4、读端关闭,写端一直写入,OS会直接杀掉写端进程,通过向目标文件发送 SIGPIPE(13) 信号,终止目标进程。

管道在通信时有以下五种状态

  • 1、匿名管道,可以允许有血缘关系的进程之间进行进程间通信,常用于父子,但匿名管道的通信也仅限于此!
  • 2、匿名管道,默认给读写端要提供同步机制
  • 3、是面向字节流的(面向字节流读取, 跟写的时候的格式无关, 读取的时候只跟字节数有关)
  • 4、管道的声明周期是跟随进程的
  • 5、管道是单向通信的,它是半双工通信的一种特殊情况。

匿名管道

匿名管道没有文件名,只有文件描述符。可以调用 pipe 函数来创建匿名管道

其中,传进去一个数组,pipefd[0] 表示读端文件描述符,pipedf[1] 表示写端文件描述符。

返回值:创建成功返回0,创建失败返回-1,并设置错误码!

当用父子进程之间创建管道来通信时:

上述的管道特点、匿名管道的原理等。都可以使用下面的代码来测试(代码有超详细注释)

下面这段代码创建了一个简单的单向通信的管道

#include <iostream>
#include <unistd.h> // 操作系统调用接口, 只能 .h 结尾(规定的)
#include <cassert>  //   以 c 开头 + C语言头文件(不加 h) 就可以重新封装一套 C 语言头文件 #include<cstdio>  #include<cstring>
#include <sys/types.h>
#include <cstdio>
#include <cstring>
#include <sys/wait.h>
using namespace std;
#define MAX 1024

int main()
{
    int pipefd[2] = {0};

    // 第一步:建立管道
    int n = pipe(pipefd); // 管道创建成功    等于 0 创建成功,创建失败返回 -1
    assert(n == 0);       // 只在 debug 下存在   assert 用于意料之外的问题!
    (void)n;              // 假装使用一下, 防止 n 没有被使用而报警
    // cout << pipefd[0] << " " << pipefd[1] << endl; // 使用 pipe 函数后 pipe[0], pipe[1] 分别自动被设置为 3,4 (未被使用的两个最小的文件描述符)
    // pipefd[0] 读端  pipefd[1] 写端

    // 第二步:创建子进程
    pid_t id = fork();
    if (id < 0)
    {
        // 创建失败
        perror("fork");
        return 1;
    }

    // 第三步:父子关闭不需要的文件描述符,形成单向通信的信道
    // 子进程写入,父进程读取
    if (id == 0)
    {
        // 子进程
        close(pipefd[0]);
        int cnt = 10;
        // 只向管道写入
        while(cnt)
        {
            char message[MAX];
            snprintf(message, sizeof(message) - 1, "Hello father, I am child, pid: %d, cut: %d", getpid(), cnt);
            cnt--;
            write(pipefd[1], message, strlen(message));
            sleep(1);
        }
        // TODO
        exit(0);
    }
    // 父进程
    close(pipefd[1]); // 父进程关闭写端 
    char buffer[MAX];
    while(true)
    {
        ssize_t n = read(pipefd[0], buffer, sizeof(buffer) - 1); // 一会要给最后一个位置加 '\0',方便 C/C++ 使用(万一读满了, 就最多读 MAX - 1 个)
        // 面向字节流读取, 跟写的时候的格式无关, 读取的时候只跟字节数有关
        if(n > 0)
        {
            buffer[n] = 0; // 自己维护字符串(为 C++ 准备的)
            cout << getpid() << " :child say:" << buffer << " to me !" <<endl;
        }
        if(n == 0) break; // 说明写端关闭了(不是写端不写,而是关闭了)读端就会关闭
    }
    // 子进程退出后才能等待到
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid == id)
    {
        // 等待成功
        cout << "wait success, child exit sig: " << (status &0x7F) << endl; // 退出信号 :status 的低 7 位
    }
    return 0;
}

使用管道技术设计一个简单的进程池

进程池(Process Pool)是一种并发编程的技术,它允许创建一组预先分配的子进程,这些子进程可以被重复地使用来执行任务。

通常情况下,每个进程都会拥有一个独立的地址空间和资源,这会导致进程的创建和销毁需要耗费大量的时间和资源。而进程池技术通过预先创建一组子进程,这些子进程会在一个池中等待任务分配。当有任务需要执行时,只需要将任务交给其中一个空闲的子进程即可,不需要重复地创建和销毁进程。

进程池通常由一个主进程(也称为管理进程)和一组子进程组成。主进程负责管理子进程的创建、销毁和任务分配,而子进程则负责执行实际的任务。

进程池的主要优势:提高效率、简化编程、提高可扩展性进程池主要适用于CPU密集型的任务,即任务主要涉及到计算和数据处理。

#include <iostream>
#include <cassert>
#include <unistd.h>
#include <functional>
#include <ctime>
#include <vector>
#include <string>
#include <sys/types.h>
#include <sys/wait.h>
typedef std::function<void()> task_t;
void Download()
{std::cout << "我是一个下载任务" << std::endl;}
void PrintLog()
{std::cout << "我是一个打印日志的任务" << std::endl;}
void PushVideoStream()
{std::cout << "这是一个推送视频流的任务" << std::endl;}
// void ProcessExit()
// {exit(0);}

class Init
{
public:
    // 任务码
    const static int g_download_code = 0;
    const static int g_printlog_code = 1;
    const static int g_pushVideostream_code = 2;
    // 任务集合
    std::vector<task_t> tasks;
public:
    Init()
    {
        tasks.push_back(Download);
        tasks.push_back(PrintLog);
        tasks.push_back(PushVideoStream);
        srand(time(nullptr) ^ getpid());
    }
    bool CheckSafe(int code)
    {
        if(code >= 0 && code < tasks.size()) return true;
        else return false;
    }
    void RunTask(int code)
    {
        return tasks[code]();
    }
    int SelectTask()
    {
        int code = rand() % tasks.size();
        return code;
    }
    std::string ToDesc(int code) //将任务码转化为任务字符传串
    {
        switch (code)
        {
            case g_download_code:
                return "g_download_code";
            case g_printlog_code:
                return "g_printlog_code";
            case g_pushVideostream_code:
                return "g_pushVideostream_code";
            default:
                return "Unknow";
        }
    }
};

Init init; // 定义对象
 
const int num = 5;
static int number = 1;
class channel // 先描述, 再组织
{
public:
    channel(int fd, pid_t id)
        : ctrlfd(fd),
          workerid(id)
    {
        name = "channel-" + std::to_string(number++);
    }

public:
    int ctrlfd;
    pid_t workerid;
    std::string name;
};

void Work()
{
    while (true)
    {
        int code = 0;
        ssize_t n = read(0, &code, sizeof(code)); // n 表示每次读到的字节个数
        if (n == 0) break;                               // 等于0 说明啥都没读到退出子进程并回收
        (void)n;
        if (!init.CheckSafe(code))
            continue; // code 不合法, 不在任务范围之内
        init.RunTask(code);
    }
}
void PrintDebug(const std::vector<channel> &c)
{
    for (const auto &channel : c)
    {
        std::cout << channel.ctrlfd << "," << channel.workerid << "," << channel.name << std::endl;
    }
}

void CreateChannels(std::vector<channel> *c)
{
    std::vector<int> old; // 用来保存父进程的写端文件描述符
    for (int i = 0; i < num; i++)
    {
        // 1. 先创建管道
        int pipefd[2];
        int n = pipe(pipefd);
        assert(n == 0);
        (void)n;

        // 2. 创建子进程
        pid_t id = fork();
        assert(id != -1);

        // 3. 构建单向信道
        if (id == 0) // child
        {
            if(!old.empty())
            {
                for(int i = 0; i < old.size(); i++)
                {
                    close(old[i]); // 关闭继承过来的父进程的文件描述符
                }
            }
            close(pipefd[1]);
            dup2(pipefd[0], 0); // 管道直接切换为从标准输入中读(重定向)
            Work();
            exit(0);
        }
        // father
        close(pipefd[0]);
        old.push_back(pipefd[1]);  //保存这次进程的写端, 方便下次的子进程关闭这些从父进程中继承过来的不需要要的端口
        c->push_back(channel(pipefd[1], id));
        // std::cout << id << std::endl;
    }
}
void SendCommand(std::vector<channel> &c, bool flag, int num = 0)
{
    int quit = 0;
    int pos = 0;
    while (true)
    {
        // 1、选择任务
        int command = init.SelectTask();
        // 2、选择信道
        const auto &channel = c[pos++];
        pos %= c.size();
        // debug
        std::cout << "send command" << 1 << "in" << channel.name <<"worker is" << std::endl;
        sleep(10);
        // 3、发送任务
        write(channel.ctrlfd, &command, sizeof(command)); // 发送任务
        // 4、判断是否要退出
        if (!flag)
        {
            num--;
            if (num <= 0)
                break;
        }
        //sleep(1);
    }
    std::cout << "SendCommand done......" << std::endl;
}
void ReleaseChannels(std::vector<channel>& c)
{
    // version 2
    int num = c.size() - 1;

    for(; num >= 0; num --)
    {
        close(c[num].ctrlfd);
        pid_t rid = waitpid(c[num].workerid, nullptr, 0);
    }
    // version 1
    // // 回收资源,想让子进程退出,释放资源,只需要关闭写端
    // for (const auto &channel : c)
    // {
    //     close(channel.ctrlfd);
    //     // pid_t rid = waitpid(channel.workerid, nullptr, 0); 如果这样做就会出现, 其他子进程的文件描述符还指向写端,导致写端无法正常关闭, 进而导致 wait 读不到数据
    // }
    // // 回收子进程
    // for (const auto &channel : c)
    // {
    //     pid_t rid = waitpid(channel.workerid, nullptr, 0);
    //     if (rid == channel.workerid)
    //     {
    //         std::cout << "wait child" << channel.workerid << "success" << std::endl;
    //     }
    // }
}
const bool g_always_loop = true;
int main()
{
    std::vector<channel> channels;
    CreateChannels(&channels);
    // PrintDebug(channels);
    SendCommand(channels, !g_always_loop, 1000);
    ReleaseChannels(channels);
    // sleep(10);
    return 0;
}

命名管道

匿名管道没有名字,可以让有血缘关系的进程,通过继承的属性,进行管道通信,而命名管道则是可以让两个好不相干的进程进行管道通信!

1、两个进程中的其中一个进程调用系统接口 mkfifo 创建命名管道文件。

创建成功返回0,创建失败返回 - 1,并设置错误码 。

2、使用这个管道文件进行通信,一个进程为读端,一个进程为写端。

一个进程使用 write 接口往这个管道文件写,一个进程使用 read 接口从这个管道文件里读,就形成一个管道式的通信。

标签:std,code,int,间通信,管道,进程,include
From: https://blog.csdn.net/zyb___/article/details/136691739

相关文章

  • 僵尸进程_ZombieProcess
            僵尸进程(ZombieProcess)在计算机操作系统中,特别是类Unix系统中,是指一种特殊的进程状态。当一个子进程已经完成了其生命周期并通过`exit()`系统调用正常退出或者异常终止时,它并不会立即从系统进程中消失。此时,虽然它的所有资源(如内存、打开的文件描述符等)都已经......
  • CSC209 系统故障 管道
    CSCSHELL截止时间为周日晚上11:59。积分2002月14日后12点开始发售。A3–CSCSHELLCSC2092024年冬季上次更新(3月15日):见5.7目录1.简介2.入门文件2.1.实际开始3.CSCSHELL4.限制(shell的东西你不必实现)5.实施和要求5.1.管理错误5.2.外壳变量5.2.1.创造5.2.2.用法5.2.3.路径5.2.4.......
  • 线程的引入以及进程和线程的区别
    线程的引入:在OS中引入线程,为了减少程序在并发执行时所付出的时空开销,使OS具有更好的并发性。进程的两个基本属性:进程是一个可拥有资源的独立单位;进程同时也是一个可独立调度和分派的基本单位。每个进程在系统中均有唯一的PCB,系统可根据PCB来感知进程的存在,也可以根据PCB中的信......
  • 多进程并发 v.s.多线程并发
    多进程并发v.s.多线程并发优缺点多进程的优点:独立性:每个进程都有独立的内存空间,相互之间不会影响,数据隔离性好。稳定性:一个进程崩溃通常不会影响其他进程,提高了系统的稳定性。简单的错误处理:进程之间的错误处理相对简单,一个进程崩溃通常只需要重启该进程即可。适用于CP......
  • Linux系统(四)- 进程初识 | 环境变量 | 进程地址空间
    ~~~~前言冯诺依曼体系结构(重要)总览CPU工作方式什么是指令集?CPU为什么只和内存打交道(数据交换)?木桶效应:在数据层面的结论程序运行为什么要加载到内存?进一步理解计算机体系结构操作系统(operatorsystem)(重要)什么是操作系统为什么要有操作系统操作系统怎样进行管理的先描述......
  • Linux进程通信补充——System V通信
    三、SystemV进程通信​SystemV是一个单独设计的内核模块;​这套标准的设计不符合Linux下一切皆文件的思想,尽管隶属于文件部分,但是已经是一个独立的模块,并且shmid与文件描述符之间的兼容性做的并不好,网络通信使用的是文件的接口,所以SystemV标准慢慢地边缘化了,很难被整......
  • 【操作系统】线程、程序、进程死锁的必要条件?如何避免死锁?死锁的预防,死锁的避免(银行
    目录线程、程序、进程死锁的必要条件?如何避免死锁?死锁的预防死锁的避免(银行家)死锁的检测进程-资源分配图死锁检测步骤死锁的解除线程、程序、进程进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行......
  • 应用使用JNDI,数据库无法连接,导致的进程无法启动问题处理
    起因数据库迁移,导致的ip改变,并且更换了用户密码。解决方法找到服务器应用目录,例如:/home/weblogic/Oracle/Middleware/user_projects/domains/abc进入config目录config.xml配置文件,属于中间件的整体配置,删除使用JNDI的应用的标签内容jdbc目录为weblogic的数据库配置文件重......
  • QT5.14.2 探秘Qt信号槽奥秘--让对象间通信如虎添翼
    一、前言在当今这个万物互联的时代,对象间通信无疑是编程领域中最为基础也最为重要的问题。作为知名的跨平台开发框架,Qt自然也需要解决这一问题。于是,Qt巧妙地提出了信号与槽(Signals&Slots)这一机制,以观察者模式的思路让对象间通信变得行云流水。那么,Qt信号与槽的本质......
  • RISC-V CPU管道模拟
    RISC-VCPU管道模拟1.简介RISC-V是源自伯克利的开源架构和指令集标准。该项目要求您基于标准的五阶段管道实现RISC-VCPU管道模拟器。您需要从RISC-V规范2.2中指定的RV32I指令设置中实现指令的子集。实施完整的CPU模拟器可以有效地行使系统编程功能,并加深对与建筑有关的知识......