【lesson44】进程间通信之匿名管道

本文详细介绍了进程通信的概念,特别是Linux中的管道机制,包括创建管道、使用fork创建子进程以及构建单向通信信道的过程。文章还展示了如何在C++代码中实现管道通信,并讨论了其特点和应用场景,如进程池和任务调度。
摘要由CSDN通过智能技术生成

理解进程通信

进程运行具有独立性—>进程想通信,难度其实是比较大的---->进程通信的本质:先让不同的进程看到同一份资源(内存空间)----->而这个内存空间不能隶属与任何一个进程,更改强调共享

为什么要进程间通信:
为了达到交互数据、控制、通知等目标

进程通信不是目的,而是手段

进程间通信目的

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

进程间通信发展

  • 管道---->Linux原生提供的通信方式
  • System V进程间通信-----多进程------单机通信
  • POSIX进程间通信----->多线程----->网络通信

他们都有一定的标准,标准在使用者看来,更多是接口上具有一定规律性

管道理解

什么是管道?
感性理解:
管道有出口,有入口
天然气管道,石油管道,自来水管道…
只能单向通信
传输的都是资源
计算机管道也是传输的资源是数据!

什么是管道?
管道是Unix中最古老的进程间通信的形式。
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
计算机通信领域的设计者,设计了一种单向通信的方式—管道
管道通信背后是进程之间通过管道进行通信。
在这里插入图片描述
1.分别一读写方式打开一个文件
在这里插入图片描述
2.fork创建子进程
在这里插入图片描述
3.双方各自关闭自己不需要的文件描述符
假设我们只要父进程写入,子进程读取
在这里插入图片描述
管道通信是纯内存形的通信方式,不要把数据写到磁盘

以前的指令

who | wc -l

其中|就是管道
在这里插入图片描述

使用管道

如何做到让不同的进程,看到同一份资源呢?-----fork让子进程继承的----能够让具有血缘关系的进程进行进程间通信—常用于父子进程

管道通信的步骤

1.创建管道

接口
在这里插入图片描述
其中参数pipefd是什么?
pipefd是输出型参数,我们调用pipe把管道的读写两端fd放进pipefd数组中。
在这里插入图片描述
开始创建pipe管道。
在这里插入图片描述
那么pipe的返回值,是什么呢?
在这里插入图片描述
因为错误会放回-1,所以我们直接进行断言,管道都没创建成功我们也没有必要通信。
在这里插入图片描述

2.fork创建子进程

在这里插入图片描述

3.构建单向通信信道

子进程读取,父进程写入
子进程保留读端关闭写端
父进程保留写端关闭读端
在这里插入图片描述

子进程构建通信

在这里插入图片描述

父进程构建通信

在这里插入图片描述

使用管道的完整版代码

#include <iostream>
#include <string>
#include <vector>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <assert.h>

int main()
{
    //1.创建管道
    int pipefd[2] = {0};//pipefd[0]是读端,pipefd[1]是写端
    int n = pipe(pipefd);
    assert(n != -1);
    (void)n;//因为后面不使用,所以直接转成(void)以免报错

    //2.创建子进程
    int id = fork();
    
    //3.构建单向通信信道,父进程写入子进程读取
    if(id == -1)
    {
        //出错
        perror("create child failed:");
    }
    else if(id == 0)
    {
        //子进程  读取
        //关闭写端
        close(pipefd[1]);
        ssize_t n = 0;
        //定义数据换出去
        char buffer[1024];

        //读取数据
        while(true)
        {
            //读取数据
            n = read(pipefd[0],buffer,sizeof(buffer));
            //read返回值差错初六
            if(n > 0)
            {
                //第n为置为'\0'因为系统不带'\0'
                buffer[n] = 0;
                //输出获取到的数据
                std::cout << "child get a message["<<getpid()<<"] Father# " << buffer << std::endl;
            }
            else if(n == 0)
            {
                //Father写端进程退出不再写数据,child读端进程也退出不再读取数据
                std::cout << "Father quit,I quit too!" << std::endl;
                break;
            }
        }
        close(pipefd[0]);
        exit(0);
    }
    else
    {
        //父进程 写入
        //关闭读端
        close(pipefd[0]);
        //要发送的数据
        std::string message = "I am father,I am writting message from you";
        int count = 0;
        char buffer[1024];
        while(true)
        {
            //构建一个能发生变化的字符串
            snprintf(buffer,sizeof(buffer),"%s[%d] : %d",message.c_str(),getpid(),count++);
            //发送数据
            write(pipefd[1],buffer,strlen(buffer));

            //故意发送数据慢点以便观察
            sleep(2);
        }

        close(pipefd[1]);
        //进程等待
        pid_t ret = waitpid(id,nullptr,0);
        std::cout << "id: " <<id << "ret: "<<ret << std::endl;
        assert(ret > 0);
        (void)ret;

    }
    return 0;
}

为什么前面我们不定义全局的buffer来进行通信呢?
因为有写时拷贝的存在,无法更改通信。

扩展

1.管道是用来进行具有血缘关系的进程之间进行通信(常用于父子进程),管道子进程读取数据会等父进程写入后再读,否则就不会读
显示器是一个文件,父子进程同时往显示器写入的时候,有没有说一个进程会等待另一个进程的情况?没有!----缺乏访问控制
管道文件—读取具有访问控制

2.管道具有通过让进程间协同的能力,因为提供了访问控制

3.管道提供面向流式服务----面向字节流

管道写的一方fd关闭,读取的一方read会返回0表示读到了文件结尾。

4.管道是基于文件的,文件的什么周期是随进程的,管道的什么周期也随进程。

管道特点

  • 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
  • 管道提供流式服务
  • 一般而言,进程退出,管道释放,所以管道的生命周期随进程
  • 一般而言,内核会对管道操作进行同步与互斥
  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

管道是单向通信就是半双工通信的一种特殊情况,既可以读内容又可以写内容,但是写内容和读内容不能同时进行。

全双工:既可以读内容又可以写内容,读和写可以同时进行。

特殊情况:
1.读快写慢,管道没有数据的时候,读的一端必须等待写的一端。
2.写块读慢,管道写满了就不能再写了。
3.写端关闭,读端读取到0,表示读到文件结尾
4.读端关闭,写端继续写OS会自动终止进程

管道的作用?
父进程可以给子进程派发任务,也称进程池
在这里插入图片描述

Task.hpp

任务代码

#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <functional>
#include <sys/types.h>
#include <unistd.h>

typedef std::function<void()> func;

std::vector<func> callbacks;
std::unordered_map<int,std::string> desc;

void readMySQL()
{
    std::cout << "sub process["<<getpid()<<"] execute read MySQL task\n" << std::endl;
}

void execuleUrl()
{
    std::cout << "sub process["<<getpid()<<"] execute execuleUrl task\n" << std::endl;
}

void encryption()
{
    std::cout << "sub process["<<getpid()<<"] execute encryption task\n" << std::endl;
}

void persistence()
{
    std::cout << "sub process["<<getpid()<<"] execute persistence task\n" << std::endl;
}

void Load()
{
    desc.insert({callbacks.size(),"readMySQL"});
    callbacks.push_back(readMySQL);

    desc.insert({callbacks.size(),"execuleUrl"});
    callbacks.push_back(execuleUrl);

    desc.insert({callbacks.size(),"encryption"});
    callbacks.push_back(encryption);

    desc.insert({callbacks.size(),"persistence"});
    callbacks.push_back(persistence);
}

void showHandler()
{
    for(const auto &iter : desc)
    {
        std::cout << iter.first << "\t" << iter.second << std::endl;
    }
}

int handlerSize()
{
    return callbacks.size();
}

ProcessPool.cc

#include <iostream>
#include <vector>
#include <unordered_map>
#include <cstdio>
#include <cstring>
#include <cassert>
#include <ctime>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include "Task.hpp"

int waitcommand(int waitfd,bool &quit)//阻塞式等待
{
    uint32_t command = 0;
    //读取指令
    ssize_t n = read(waitfd,&command,sizeof(command));
    if(n == 0)
    {
        quit = true;
        return -1;
    }
    
    assert(n == sizeof(uint32_t));
    return command;
}
void sendAndWakeup(pid_t who,int fd,uint32_t command)
{
    //发送指令
    write(fd,&command,sizeof(command));

    std::cout << "main process: call process " << who << " execute " << desc[command] << " through " << fd << std::endl;
    
}

#define PROCESS_NUM 5
int main()
{
    //加载任务
    Load();

    // 存储pid : pipefd的映射
    std::vector<std::pair<pid_t, int>> slots;
    
    // 创建子进程
    for (int i = 0; i < PROCESS_NUM; i++)
    {
        // 创建管道
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        assert(n == 0);

        //创建子进程
        int id = fork();
        if (id < 0)
        {
            perror("create chlid failed");
        }
        else if (id == 0)
        {
            // 子进程
            close(pipefd[1]);

            
            while (true)
            {
                bool quit = false;
                //读取任务指令
                int command = waitcommand(pipefd[0], quit);
                if (quit)
                    break;

                //执行任务
                if (command >= 0 && command < callbacks.size())
                {
                    callbacks[command]();
                }
                else
                {
                    std::cout << "the command unlawful" << std::endl;
                }
            }

            exit(1);
        }
        else
        {
            // 父进程,关闭读端
            close(pipefd[0]);
            //将每个进程的pid和pipefd存入pair<pid_t,int>数组中
            slots.push_back(std::pair<pid_t, int>(id, pipefd[1]));
        }
    }

    // 父进程派发任务
    srand((unsigned long)time(nullptr) ^ getpid() ^ 23323123123L); // 让数据源更随机

    while (true)
    {
        int select = 0;
        int command = 0;
        printf("*****************************************\n");
        printf("**1.show handlers        2.send command**\n");
        printf("*****************************************\n");

        std::cout << "Please Enter Select:";
        std::cin >> select;

        if (select == 1)
        {
            showHandler();
        }
        else if (select == 2)
        {
            std::cout << "Please Enter command:";
            //选择任务
            std::cin >> command;
            //随机选择进程
            int choice = rand() % slots.size();
            //发送任务函数
            sendAndWakeup(slots[choice].first,slots[choice].second,command);
        }
        else
        {
            std::cout << "the select unlawful" << std::endl;
        }
    }

    //关闭所有子进程的fd
    for(const auto& slot :  slots)
    {
        close(slot.second);
    }

    //等待子进程退出,回收子进程所有信息
    for(const auto& slot :  slots)
    {
        waitpid(slot.first,nullptr,0);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值