【linux操作系统】进程间通信--管道

目录

管道的特点

匿名管道

命名管道


进程间通信 的本质是让不同的进程看到同一份资源。而管道就在此处作同一份资源。

管道是用来进程间通信的一种方式。可以将管道理解为一个内核级缓冲区,用来暂时存储进程间的数据。linux下一切皆文件,通过write,read等系统调用来访问管道中的数据。

管道的特点

1.单项通信,即只允许一方说话。

2.管道是面向字节流的。简单来说就是输入进管道的东西不一定会一次性读出来,自己可以规定读取的边界。

3.管道的生命周期随进程,本质是文件。

4.管道通信对于多进程而言自带同步互斥

匿名管道

匿名管道本质上来说就是访问这一处的资源不需要路径+文件名。只能被用来没有血缘关系间的进程间的通信。

创建方法

pipefd[0],pipefd[1]分别为读端和写端。

cpp 复制代码
int pipefd[2];
pipe(pipefd);

父子进程利用匿名管道通信有两个约束点,同步和互斥,同步的意思是访问管道要有顺序性,互斥的意思是同一时间只能有一个人访问资源,会有四种情况出现。(以子写父读为例)

1.子进程写的慢,父进程阻塞,给写机会。

2.父进程读的慢,写满后子进程阻塞,给读机会。

3.读端在读,写端关闭,读完管道内容后read返回0。

4.写端在写,读端关闭,os直接杀死子进程。

下面给出一个进程池的demo

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <string.h>
#include <cstdio>
#include <vector>
#include <functional>
#include <time.h>
#include <stdlib.h>
#include <sys/wait.h>

//////////////////////////////////////////////////////////////任务

typedef void (*mytask)(void); // 函数指针

void Sendmsg()
{

    std::cout << getpid() << " : 发送信息!!" << std::endl;
    return;
}

void Fflush()
{
    std::cout << getpid() << " : 刷新刷新!!" << std::endl;
    return;
}

void Sleep()
{
    std::cout << getpid() << " : 休眠一下!!" << std::endl;
    return;
}

void OpenAi()
{
    std::cout << getpid() << " : 打开ai软件!!" << std::endl;
    return;
}

void Close()
{
    std::cout << getpid() << " : 关机 !!!" << std::endl;
    return;
}

// 函数指针数组
mytask cb_task[5] = {Sendmsg, Fflush, Sleep, OpenAi, Close};

//////////////////////////////////////////////////////////////进程池
enum
{
    OK = 0,
    pipe_err,
    fork_err
};

const int gprocessnum = 5; // 五个子进程

// typedef std::function<void (int)> task;

using task_t = std::function<void(int)>; // 给函数类型重命名,这个函数要返回值是void,参数是int

void Dotask(int fd)
{
    // do task

    while (true)
    {
        int intask = 0;
        size_t s = read(fd, &intask, sizeof(intask));
        if (s == sizeof(intask))
        {
            if (intask <= 4 && intask >= 0)
                std::cout << intask << " ";
            cb_task[intask]();
        }
        else if (s == 0)
        {
            break;
        }
    }
    close(fd);
    exit(0);
    // sleep(100);
}

// 进程池
class proccess_pool
{
public:
    proccess_pool()
    {
    }
    ~proccess_pool()
    {
    }
    void Init(task_t cb)
    {
        creat_process_channel(cb);
    }

    void Debug()
    {
        for (auto &ch : channels)
        {
            ch.print_info();
        }
    }
    void run()
    {
        int count = 5;
        while (count)
        {
            // 选择一个任务
            int intask = select_task();
            // 选择一个进程
            int intpro = select_process();
            // 将任务发送给进程
            send_task2process(intask, intpro);
            sleep(1);
            count--;
        }
    }
    void quit()
    {
        for (auto &ch1 : channels)
        {
            ch1.channel_close();
        }
        for (auto &ch2 : channels)
        {
            ch2.pro_wait();
        }
        return;
    }

private:
    class Channel
    {
    public:
        Channel(int wfd, pid_t id)
            : _wfd(wfd), _id(id)
        {
            _sub_name = "channel_name : " + std::to_string(_id);
        }
        ~Channel()
        {
        }
        void print_info()
        {
            printf("写端编号:%d, 子进程:%d,  %s\n", _wfd, _id, _sub_name.c_str());
        }
        int Re_wfd()
        {
            return _wfd;
        }
        void channel_close()
        {
            printf("关闭wfd:%d\n", _wfd);
            close(_wfd);
        }
        void pro_wait()
        {
            printf("子进程回收:%d\n", _id);
            sleep(1);
            pid_t rid = waitpid(_id, nullptr, 0);
            (void)rid;
        }

    private:
        int _wfd;  // 记录打开的管道的写端
        pid_t _id; // 以及对应的子进程的pid
        std::string _sub_name;
    };

    void creat_process_channel(task_t cb)
    {
        for (int i = 0; i < gprocessnum; i++)
        {

            // 创建管道
            int pipefd[2] = {0};
            int n = pipe(pipefd);
            if (n < 0)
            {
                std::cerr << "pipe errro" << std::endl;
                exit(pipe_err); // 不用return不用返回
            }
            // 创建子进程
            pid_t id = fork();
            if (id < 0)
            {
                std::cerr << "fork errro" << std::endl;
                exit(fork_err);
            }
            else if (id == 0)
            {

                close(pipefd[1]); // 子进程读取,关闭写端
                // Dotask(pipefd[0]);子进程只能干一件事

                // 关闭子进程多余的写端
                for (auto &ch : channels)
                {
                    close(ch.Re_wfd());
                }

                cb(pipefd[0]); // 回调函数 cb就是Dotask 以后可以通过初始化来给子进程传递不同的任务

                exit(0);
            }
            else
            {
                close(pipefd[0]);
                // Channel ch(pipefd[1], id);
                // channels.push_back(ch);
                printf("子进程创建成功!!pid : %d\n", id);
                sleep(1);
                channels.emplace_back(pipefd[1], id);
            }
        }
    }
    // 采用轮询的方式选择进程
    int select_process()
    {

        static int n = 0; // 这行命令只会执行一次
        n++;
        n %= 5;
        return n;
    }

    int select_task()
    {
        srand(time(nullptr));
        int index = rand() % 5; // 将会有五个任务
        return index;           // 返回一个0-4的整数
    }

    void Write(int a, int b)
    {
        // 第几个进程就对应着第几个通道嘛
        int n = write(channels[b].Re_wfd(), &a, sizeof(a)); // 规定一次写4字节
        (void)n;
    }

    // 给子进程发送任务
    void send_task2process(int a, int b) // a是任务编号,b是进程编号
    {
        // 这是父进程写的过程
        // 写过去的也只会是任务的编号
        Write(a, b);
        return;
    }

    std::vector<Channel> channels;
};

int main()
{
    proccess_pool pp;
    pp.Init(Dotask);
    pp.Debug();
    pp.run();
    pp.quit();
    

    return 0;
}

命名管道

命名管道与匿名管道相对,命名管道有自己的路径+文件名。用于两个完全不相关的进程间发生通信。文件相关的核心结构只会存在一份。管道文件里面的内容只会存在于缓冲区,不会刷新到磁盘上。

使用int mkfifo(const char *filename,mode_t mode)函数创建命名管道。第一个参数表示管道名称,第二个参数表示管道权限。同样是用write,read等系统调用对管道进行访问。

下面是一段利用命名管道通信的demo。

cpp 复制代码
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <errno.h>
#include <cerrno>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#define SIZE 1024
const char *name = "./pipe";

#define READ_OPEN 1  // 0001
#define WRITE_OPEN 2 // 0010
class namepipe
{
public:
    namepipe(const std::string name_pipe = name, mode_t mode = 0666)
        : _pipe_name(name_pipe), _mode(mode), _fd(-1)
    {
    }
    void Build()
    {
        umask(0);
        if (Isexit())
        {
            std::cout << "管道已经存在!!!" << std::endl;
            return;
        }
        // 文件不存在就创建
        int p = mkfifo(_pipe_name.c_str(), _mode);
        if (p < 0)
        {
            std::cerr << "mkfifo error : " << strerror(errno) << " errno " << errno << std::endl;
        }
        else
        {
            std::cout << "管道创建成功!!!" << std::endl;
        }
    }
    void Open(int flag)
    {
        // 读方式打开
        if (flag & READ_OPEN)
        {
            std::cout << "开始打开文件" << std::endl;
            _fd = open(_pipe_name.c_str(), O_RDONLY);
            if (_fd < 0)
            {
                std::cerr << "open error : " << strerror(errno) << " errno " << errno << std::endl;

                std::cout << "文件打开失败!!" << std::endl;
                exit(-1);
            }
            char outbuffer[SIZE];
            while (true)
            {
                ssize_t s = read(_fd, outbuffer, sizeof(outbuffer) - 1);
                if (s > 0)
                {
                    std::cout << outbuffer << std::endl;
                }
                else if (s == 0)
                {
                    close(_fd);
                    break;
                }
            }
        }
        // 以写方式打开
        else if (flag & WRITE_OPEN)
        {
            _fd = open(_pipe_name.c_str(), O_WRONLY);
            if (_fd < 0)
            {
                std::cout << "文件打开失败!!" << std::endl;
                exit(-1);
            }
            int cnt = 10;
            while (cnt--)
            {
                std::cout << "Please write your msg :";
                std::string inbuffer;
                std::getline(std::cin, inbuffer);
                int n = write(_fd, inbuffer.c_str(), inbuffer.size());
                // std::cout << inbuffer << std::endl;
            }
            close(_fd);
        }
        else
        {
        }
    }

    void Delete()
    {
        if (!Isexit())
        {
            return;
        }
        // 存在就删除
        int n = unlink(_pipe_name.c_str());
        if (n == 0)
        {
            std::cout << "成功删除" << std::endl;
        }
        else
        {
            std::cerr << "unlink error : " << strerror(errno) << " errno " << errno << std::endl;
        }
    }

    ~namepipe()
    {
    }

private:
    const std::string _pipe_name;
    mode_t _mode;
    pid_t _fd;
    bool Isexit(const char *name = "./pipe")
    {
        struct stat buffer;
        int n = stat(name, &buffer);
        if (n == 0)
            return true;
        else
            return false;
    }
};

完。。。

相关推荐
gameboy0312 小时前
在Nginx上配置并开启WebDAV服务的完整指南
java·运维·nginx
异步的告白2 小时前
嵌入式Linux学习-Makefile基本语法:目标、依赖、命令、伪目标.PHONY
linux
!沧海@一粟!2 小时前
VMware升级操作指南与常见问题
linux·运维·服务器
Xzq2105092 小时前
以太网协议 —— 数据链路层
服务器·网络·网络协议
是小小张呀2 小时前
Linux系统查看常用命令
linux
Apibro2 小时前
【Linux 】解压/压缩命令全解析:unzip、tar、gzip、bzip2、xz
linux
火车叼位2 小时前
Docker 全量备份恢复实战:可离线、可迁移、可复原的标准方案
运维·docker
IMPYLH2 小时前
Linux 的 base32 命令
linux·运维·服务器·bash·shell
7yewh2 小时前
MCU 卷积神经网络部署 · 深度技术指南
linux·嵌入式硬件·ai·嵌入式