Linux进程间通信【匿名管道和命名管道】

简介: Linux进程间通信,包括匿名管道和命名管道的原理、操作及实现,两种管道实现进程池等丰富内容,详细讲解,干货满满!

Linux进程间通信【匿名管道和命名管道】

进程间通信,就是为了让两个不同进程间协作完成任务,通信的前提就是要构建两个进程之间的联系,构建联系的方法有很多种,本文先来谈谈管道通信

1. 进程间通信介绍

先来介绍一下进程间通信的相关概念

1.1 进程间通信目的

进程间通信的四个目的

  1. 数据传输:一个进程需要把自己的数据传输给另外一个进程
  2. 资源共享:多个进程之间共享同样的资源
  3. 通知事件:一个进程需要向另外一个或多个进程发送消息,通知它们发生某种事件
  4. 进程控制:有些进程需要完全控制另外一个进程的执行

1.2 进程间通信要求

进程间是有独立性的,不能违背这一原则,所以不能让一个进程直接访问另一个进程的数据

  • 要让两个不同的进程进行通信,前提条件就是:==先让两个进程看到同一份“资源”==,这也是进程间通信的本质,这里的“资源”就由操作系统直接或间接的提供

那么后续使用的任何进程间通信手段,无非就是要解决两个问题

  1. 想办法,先让不同的进程看到同一份“资源”
  2. 让一方写入,另一方读取,完成通信过程。至于通信目的与后续工作,要结合具体场景

1.3 进程间通信分类

管道:很古老的通信方式

  • 匿名管道
  • 命名管道

System V 标准:本地化进程通信方式

  • 消息队列
  • 共享内存
  • 信号量

POSIX标准:网络中进程通信方式

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量(互斥锁)
  • 条件变量
  • 读写锁

2. 匿名管道

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

在命令行中输入|`即可使用管道,我们利用管道来统计一下当前用户登录个数

who是一个命令,本质它是一个进程,是bash的子进程,wc同样是一个bash的子进程,通过管道的方式把who进程的数据也让wc进程看到

Linux下一切皆文件,管道也是文件,who进程一般都是从标准输出中获取的信息,wc进程一般都是从标准输入中获取信息的,将who进程的标准输出重定向到管道这个文件中,wc进程则将标准输入重定向到管道文件中,所以这样就可以实现两个进程看到同一份“资源”了

2.1 工作原理

借助上面的例子来讲解一下工作原理

  • 创建子进程,只会复制进程相关的数据结构对象,并不会拷贝父进程的文件对象。所以上述两个进程中文件描述符表中每个指针的指向的都是同一个文件对象
  • 这就是fork之后,父子进程会向同一个显示器打印数据的原因。这里OS提供的内存文件就是管道文件,这样就可以达成共享同一"资源"。管道文件不是放在磁盘中的,是一个内存文件,它只是支持单向通信(半双工)
  • 管道文件是确定数据流向的,关闭不需要的fd,也就是当who进程进行writewc进程进行read的时候,此时who进程就会关闭read对应的描述符(读端),wc进程就会关闭write对应的描述符(写端),这样就可以实现管道之间的单向关系了。这里的这个管道也就是匿名管道,不知道它对应的路径,也不知道对应的文件等等

2.2 pipe函数

#include <unistd.h>

int pipe(int pipefd[2]);

函数详解

  • pipe()创建一个管道,这是一个单向数据通道,可用于进程间通信。数组pipefd用于返回2引用管道两端的文件描述符。pipefd[0]表示管道的读端pipefd[1]表示管道的写端。写入管道写端的数据被内核缓冲,直到从管道的读端读取
  • 如果匿名管道创建成功,返回0,失败则返回-1,并适当地设置errno

参数记忆

  • pipefd[0]0就像嘴巴 ,嘴巴是用来读书的,所以对应读端
  • pipefd[1]1就像钢笔,钢笔是用来写字 ,所以对应写端

下面来个代码案例

#include <iostream>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <cstdlib>
#include <string>
#include <stdio.h>
#include <sys/types.h>
using namespace std;

int main()
{
   
   
    //1. 创建匿名管道
    int pipefd[2] = {
   
   0};
    int ret = pipe(pipefd);
    if(ret == -1)
    {
   
   
        cout << "pipe failed, errno: " << errno << strerror(errno) << endl;
    }

    cout << "pipefd[0]:" << pipefd[0] << endl;  //fd:3 -> 读端 
    cout << "pipefd[1]:" << pipefd[1] << endl;  //fd:4 -> 写端

    //2. 创建子进程
    pid_t id = fork();
    assert(id != -1);
    if(id == 0) //子进程(写入)
    {
   
   
        close(pipefd[0]); //关闭读端

        //4. 开始通信
        int cnt = 5;
        char buffer_child[1024]; //缓冲区
        while(cnt--)
        {
   
   
            snprintf(buffer_child, sizeof(buffer_child), "我是子进程, 我的PID是: %d, cnt: %d", getpid(), cnt + 1);
            write(pipefd[1], buffer_child, strlen(buffer_child));
            sleep(1);
        }
        exit(0);    
    }

    //父进程(读取):
    //3. 关闭不需要的fd(父进程读取,子进程写入)
    close(pipefd[1]); //关闭写端
    char buffer_parent[1024];
    int num = 5;
    while(num--)
    {
   
   
        int n = read(pipefd[0], buffer_parent, sizeof(buffer_parent) - 1);
        if(n == -1)
        {
   
   
            cout << "read failed, errno: " << errno << strerror(errno) << endl;
        }
        buffer_parent[n] = '\0';
        cout << "我是父进程, 子进程给我传递的信息是:" << buffer_parent << endl;
    }
    return 0;
}

站在文件描述符的角度深入理解管道

站在内核的角度理解管道的本质

所以,看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想”

3. 管道读写规则

管道读写规则

  • 当没有数据可读时
    • read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
    • read调用返回-1errno值为EAGAIN
  • 当管道满的时候
    • write调用阻塞,直到有进程读走数据
    • 调用返回-1errno值为EAGAIN
  • 如果所有管道写端对应的文件描述符被关闭,则read返回0
  • 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出
  • PIPE_BUF 为管道大小,Linux 中为 4096 字节
    • 当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性
    • 当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性

4. 管道特点

管道的五个特点

  1. 管道是单向通信(半双工)
  2. 管道本质是文件,文件描述符声明周期是随进程的,管道的声明周期是随进程的
  3. 不仅父子进程可以通信,爷孙进程也可以通信,兄弟进程也可以通信,所以管道通信通常用来具有"血缘关系"的进程进行进程间通信。(pipe()打开管道并不清楚管道名字 --> 匿名管道)
  4. 写入的次数和读取的次数不是严格匹配的,可能一次写的东西分很多次读,可能多次写的东西一次读取,读写没有强相关
  5. 如果一个进程read端读取完管道所有数据,对方如果不发,那么只能等待;如果write端写满管道了,不能够写了;管道具有一定的协同能力,让读端和写端能够按照顺序通信(自带同步机制)

5. 管道的特殊场景

管道有四种特殊场景,即管道为空,管道为满,写端关闭,读端关闭,前两种场景比较简单,为空会造成读端阻塞,未满会造成写端阻塞,来演示一下后两种场景

1、关闭写端,读端没有关闭

#include <iostream>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <cstdlib>
#include <string>
#include <stdio.h>
#include <sys/types.h>
using namespace std;

int main()
{
   
   
    //1. 创建匿名管道
    int pipefd[2] = {
   
   0};
    int ret = pipe(pipefd);
    if(ret == -1)
    {
   
   
        cout << "pipe failed, errno: " << errno << strerror(errno) << endl;
    }

    cout << "pipefd[0]:" << pipefd[0] << endl;  //fd:3 -> 读端 
    cout << "pipefd[1]:" << pipefd[1] << endl;  //fd:4 -> 写端

    //2. 创建子进程
    pid_t id = fork();
    assert(id != -1);
    if(id == 0) //子进程(写入)
    {
   
   
        close(pipefd[0]); //关闭读端

        //4. 开始通信
         int cnt = 0;
        while(true)
        {
   
   
            char x = 'A';
            write(pipefd[1], &x, 1);
            cout << "cnt:" << cnt << endl;
            sleep(1);
            break;
        }
        close(pipefd[1]);
        exit(0);    
    }

    //父进程(读取):
    //3. 关闭不需要的fd(父进程读取,子进程写入)
    close(pipefd[1]); //关闭写端
    char buffer_parent[1024];
    while(true)
    {
   
   
        int n = read(pipefd[0], buffer_parent, sizeof(buffer_parent) - 1);
        if(n == -1)
        {
   
   
            cout << "read is anomaly" << endl;
        }
        if(n == 0)
        {
   
   
            cout << "read end of file" << endl;
            break;
        }
        buffer_parent[n] = '\0';
        cout << "我是父进程, 子进程给我传递的信息是:" << buffer_parent << endl;
    }
    close(pipefd[0]);
    return 0;
}

关闭写端,读端没有关闭,读端再去读取数据,read就会返回0,也就是读到了文件结尾

  • 管道是单流向通信,写端关闭也就不会再有数据写入,因此当读端把剩余数据都读取后,每次都是读取0字节数据,表明此时已经读到了结尾,读端也就结束读取了

2、关闭读端,写端没有关闭

#include <iostream>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <cstdlib>
#include <string>
#include <stdio.h>
#include <sys/types.h>
using namespace std;

int main()
{
   
   
    //1. 创建管道
    int pipefd[2] = {
   
   0};
    int fd_result = pipe(pipefd);
    if(fd_result == -1)
    {
   
   
        cout << "pipe failed, errno: " << errno << strerror(errno) << endl;
    }
    cout << "pipefd[0]:" << pipefd[0] << endl;  //fd:3 -> 读端 
    cout << "pipefd[1]:" << pipefd[1] << endl;  //fd:4 -> 写端

    //2. 创建子进程
    pid_t id = fork();
    assert(id != -1);
    if(id == 0) //子进程(写入)
    {
   
   
        close(pipefd[0]); //关闭读端

        //4. 开始通信
        int cnt = 0;
        while(true)
        {
   
   
            char x = 'A';
            write(pipefd[1], &x, 1);
            cout << "cnt:" << cnt << endl;
            sleep(1);
        }
        close(pipefd[1]);
        exit(0);    
    }

    //父进程(读取):
    //3. 关闭不需要的fd(父进程读取,子进程写入)
    close(pipefd[1]); //关闭写端
    char buffer_parent[1024];
    while(true)
    {
   
   
        //sleep(10);
        int n = read(pipefd[0], buffer_parent, sizeof(buffer_parent) - 1);
        if(n == -1)
        {
   
   
            cout << "read is anomaly" << endl;
            break;
        }
        if(n == 0){
   
   
            cout << "read end of file" << endl;
            break;
        }
        buffer_parent[n] = '\0';
        cout << "我是父进程, 子进程给我传递的信息是:" << buffer_parent << endl;
        sleep(1);
        break;
    }
    close(pipefd[0]);
    return 0;
}

关闭读端,写端没有关闭,这样就没有意义,OS不会允许浪费资源的行为,OS会通过信号直接kill掉一直在写入的进程,13号信号就是用来终止管道写端进程的

6. 匿名管道进程池

实现功能:

  • 父进程创建一批子进程,通过多条匿名管道与它们链接通信,向子进程下达指定的任务

实现模型

实现源码

Task.hpp

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

class manage_child;
class Task;

#define CHILD_PROCESS_NUM 3

void create_frame(std::vector<manage_child>& manage);
void control_child_process(const std::vector<manage_child>& manage);
void recycle_process(const std::vector<manage_child>& manage);

class manage_child
{
   
   
public:
    manage_child(int fd, int id)
        :_write_fd(fd)
        ,_child_id(id)
    {
   
   }

    ~manage_child(){
   
   }
public:
    int _write_fd;
    pid_t _child_id;
};

typedef void(*fun_p)();
#define GET_NET 1
#define POLL_SERVER 2
#define PUSH_SOURCE 3
void get_net();
void poll_server();
void push_source();

class Task
{
   
   
public:
    Task()
    {
   
   
        funcs.push_back(get_net);
        funcs.push_back(poll_server);
        funcs.push_back(push_source);
    }

    void execute_task(int option){
   
   
        assert(option >= 1 && option <= 3);
        funcs[option - 1]();
    }

    ~Task(){
   
   }
private:
    std::vector<fun_p> funcs;
};

ctrlProcess.cc

#include "ctrlProcess.hpp"

void get_net()
{
   
   
    std::cout << "子进程:PID:" << getpid() <<  "获取网络资源中........." << std::endl;
}
void poll_server()
{
   
   
    std::cout << "子进程:PID:" << getpid() <<  "下载服务资源中........" << std::endl;
}
void push_source()
{
   
   
    std::cout << "子进程:PID:" << getpid() <<  "发送服务资源中........" << std::endl;
}

void execute_command(int read_fd)
{
   
   
    Task task;
    while(true){
   
   
        int command = 0;
        int read_return = read(read_fd, &command, sizeof(int));
        if(read_return == sizeof(int)){
   
   
            //执行任务
            task.execute_task(command);
        }else{
   
   
            break;
        }
    }
}

void create_frame(std::vector<manage_child>& manage)
{
   
   
    std::vector<int> fds;
    for(int i = 0; i < CHILD_PROCESS_NUM; ++i){
   
    
        //创建管道
        int pipefd[2];
        int pipe_return = pipe(pipefd);
        assert(pipe_return == 0);
        (void)pipe_return;
        //创建子进程
        pid_t id = fork();
        assert(id != -1);
        if(id == 0){
   
    //子进程 - 读
            for(auto& fd : fds) close(fd); //关闭子进程拷贝父进程的写端
            close(pipefd[1]);
            //dup2(pipefd[0], 0);
            execute_command(pipefd[0]);
            close(pipefd[0]);
            exit(0);
        }
        //父进程
        close(pipefd[0]);
        //父进程对子进程和写端组织管理
        manage.push_back(manage_child(pipefd[1], id)); 
        fds.push_back(pipefd[1]);
    }
}

void show_option(){
   
   
    std::cout << "-----------------------------------------" << std::endl;
    std::cout << "---  1. 获取网络资源   2. 下载服务资源  ---" << std::endl;
    std::cout << "---  3. 发送服务资源   4. 退出服务系统  ---" << std::endl;
    std::cout << "-----------------------------------------" << std::endl;
}

void control_child_process(const std::vector<manage_child>& manage)
{
   
   
    while(true){
   
   
        //选择任务
        show_option();  
        std::cout << "请选择->";
        int command = 0;
        std::cin >> command;
        if(command == 4) break;
        if(command < 1 || command > 3) continue;
        //选择进程
        int rand_index = rand() % manage.size();
        std::cout << "被选中的子进程:" << manage[rand_index]._child_id << std::endl;
        //分发任务
        write(manage[rand_index]._write_fd, &command, sizeof(command));
        sleep(1);
    }   
}

void recycle_process(const std::vector<manage_child>& manage)
{
   
   
    for(int i = 0; i < manage.size(); ++i){
   
   
        std::cout << "父进程让子进程退出" << manage[i]._child_id << std::endl;
        close(manage[i]._write_fd);
        waitpid(manage[i]._child_id, nullptr, 0); //阻塞式等待
        std::cout << "父进程回收子进程:" << manage[i]._child_id << std::endl;
    }
    sleep(5); //父进程退出
}

int main()
{
   
   
    //创建框架:一个父进程控制多个子进程(父进程写,子进程读)
    std::vector<manage_child> manage; 
    create_frame(manage);

    //父进程控制任意子进程执行任务
    control_child_process(manage);

    //回收子进程并关闭管道
    recycle_process(manage);
    return 0;
}

7. 命名管道

命名管道的特点是自带同步与互斥机制、数据单向流通,与匿名管道不同的是,命名管道有自己的名字,因此可以被没有血缘关系的进程看到,所以命名管道可以实现两个独立进程间通信

7.1 概念理解

命名管道(named pipe)又被称为先进先出队列(FIFO),是一种特殊的管道,存在于文件系统中。命名管道与管道非常类似,但是又有自身的显著特征:

  • 命名管道可以用于任何两个进程间的通信,而不限于同源的两个进程
  • 命名管道作为一种特殊的文件存放在文件系统中,而不是像匿名管道那样存放在内核中。当进程对命名管道的使用结束后,命名管道依然存在于文件系统中,除非对其进行删除操作,否则该命名管道不会自行消失
  • 命名管道就是给匿名管道起名字,给匿名管道这个内存文件分配inode,将文件名与之构建联系,但是不给它分配 Data block,它是一个内存文件不需要将数据刷盘到磁盘中

7.2 创建和使用

使用系统调用接口mkfifo(),来创建命名管道

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

mkfifo()函数

  • pathname参数:创建命名管道文件时的路径和名字,既可以传递绝对路径也可以传递相对路径
  • mode参数:创建命令管道文件时的权限,mode_t类型就是对 unsigned int的封装,等价于uint32_t
  • 返回值:创建成功返回0,失败则返回-1

mkfifo还可以直接在命令行创建命名管道文件

mkfifo 命名管道文件名  //创建命名管道文件

可见管道文件的类型为p,大小为0,这也就说明管道文件就是一个内存级文件,有自己的上限,在文件系统中只是挂了个名

使用系统调用接口unlink(),来删除命名管道

#include <unistd.h>

int unlink(const char *pathname);

返回值和参数作用和mkfifo()相同,用于删除指定命名管道

当然也可以在命令行使用

unlink 命令管道文件名    //删除命名管道文件

7.3 不同进程间通信

我们可以利用命名管道来模拟两个进程间的通信

  • 服务端server创建管道文件,以读的方式打开,客户端client以写的方式打开管道文件,打开后让两个进程通信,通信结束后,客户端关闭写端,服务端读取到0后关闭并删除命名管道文件

common.hpp 公共资源

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include <assert.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
#include <strings.h>

const std::string path_name = "./fifo"; //管道名
#define MODE 0664 //权限

client.cc

#include "common.hpp"

int main()
{
   
   
    //客户端
    //打开文件
    int open_fd = open(path_name.c_str(), O_WRONLY);
    assert(open_fd != -1);

    //写入数据,进行通信
    while(true){
   
   
        char buffer[1024];
        if(strcasecmp(buffer, "quit") == 0){
   
   
            break;
        }
        std::cout << "请输入:>";
        char* message = fgets(buffer, sizeof(buffer), stdin);
        buffer[strlen(buffer) - 1] = '\0';
        assert(message != nullptr);
        (void)message;
        write(open_fd, buffer, strlen(buffer));
    }
    close(open_fd);

    return 0;
}

sercer.cc

#include "common.hpp"

int main()
{
   
   
    //服务端
    //创建命名管道
    umask(0);
    int mkfifo_ret = mkfifo(path_name.c_str(), MODE);
    assert(mkfifo_ret != -1);

    //打开文件并读取
    int open_fd = open(path_name.c_str(), O_RDONLY);
    assert(open_fd != -1);

    while(true){
   
   
        char buffer[1024];
        ssize_t size = read(open_fd, buffer, sizeof(buffer) - 1);
        if(size > 0){
   
   
            buffer[size] = '\0';
            std::cout << buffer << std::endl;
        }else{
   
   
            break;
        }
    }

    close(open_fd);

    //关闭命名管道
    int unlink_ret = unlink(path_name.c_str());
    assert(unlink_ret != -1);

    return 0;
}

7.4 两种管道的区别

命名管道与匿名管道的区别

  • 匿名管道只能用于具有血缘关系的进程间通信,命名管道谁都可以用
  • 匿名管道直接通过pipe函数创建使用,命名管道需要先通过mkfifo 函数创建,然后再通过open打开使用
  • 出现多条匿名管道时,可能会出现写端fd重复继承的情况,命名管道不会出现这种情况

8. 命名管道进程池

ControlCenter.cc

#include "NamepipePool.hpp"

void create_namepipe()
{
   
   
    int fifo_net_request_ret = mkfifo(fifo_net_request.c_str(), MODE);
    assert(fifo_net_request_ret != -1);
    int fifo_dispose_data_ret = mkfifo(fifo_dispose_data.c_str(), MODE);
    assert(fifo_dispose_data_ret != -1);
    int fifo_manage_system_ret = mkfifo(fifo_manage_system.c_str(), MODE);
    assert(fifo_manage_system_ret != -1);
}

Manage_fifo manage_namepipe()
{
   
   
    std::cout << "打开fifo_net_request......" << std::endl;
    sleep(1);
    int fifo_net_request_fd = open(fifo_net_request.c_str(), O_RDWR);
    assert(fifo_net_request_fd != -1);
    std::cout << "打开fifo_dispose_data......." << std::endl;
    sleep(1);
    int fifo_dispose_data_fd = open(fifo_dispose_data.c_str(), O_RDWR);
    assert(fifo_dispose_data_fd != -1);
    std::cout << "打开fifo_manage_system........" << std::endl;
    sleep(1);
    int fifo_manage_system_fd = open(fifo_manage_system.c_str(), O_RDWR);
    assert(fifo_manage_system_fd != -1);
    Manage_fifo center(   fifo(fifo_net_request_fd, fifo_net_request)
                        , fifo(fifo_dispose_data_fd,fifo_dispose_data)
                        , fifo(fifo_manage_system_fd,fifo_manage_system));
    std::cout << "命名管道全部打开!" << std::endl;
    return center;
}

void task_menu()
{
   
   
    std::cout << "----------------------------------------------" << std::endl;
    std::cout << "-- 1.网络请求                     2.数据处理 --" << std::endl;
    std::cout << "-- 3.系统管理                     4.退出中控 --" << std::endl;
    std::cout << "----------------------------------------------" << std::endl;
}

void write_fifo(int& command, Manage_fifo& center)
{
   
   
    int buf = command;
    for(int i = 0; i < PROCESS_NUM; ++i){
   
   
        if(command - 1 == i){
   
   
            write(center._fifos[i]._fifo_fd, &buf, sizeof(buf));
            break;
        }
    }
}

void close_fifo(Manage_fifo& center)
{
   
   
    for(int i = 0; i < PROCESS_NUM; ++i){
   
   
        close(center._fifos[i]._fifo_fd);
        unlink(center._fifos[i]._fifo_name.c_str());
    }
}

//主控进程管理命令管道写入->任务进程读取执行
int main()
{
   
   
    //1. 创建管道文件
    create_namepipe(); 
    //2. 打开文件做管理
    Manage_fifo center;
    try
    {
   
   
        center = manage_namepipe(); //TODO(拷贝构造)
    }
    catch(const std::exception& e)
    {
   
   
        std::cerr << e.what() << '\n';
    }

    std::cout << "开始执行任务" << std::endl;
    //3. 通过选择执行任务
    while(true)
    {
   
   
        //1. 选择执行任务
        // > 列出菜单
        task_menu();
        // > 输入
        int command = 0;
        std::cout << "请求输入#";
        std::cin >> command;
        if(command == 4){
   
   
            break;
        }
        if(command < 1 || command > 4){
   
   
            std::cout << "输入错误,请重新输入!";
            continue;
        }
        //2. 执行选择写入对应进程
        write_fifo(command, center);
        sleep(1);
    }
    //3. 关闭所有命名管道
    close_fifo(center);
    (void)center;
    return 0;
}

DisposeData.cc

#include "NamepipePool.hpp"

int main()
{
   
   
    int fd = open(fifo_dispose_data.c_str(), O_RDWR);
    assert(fd != -1);
    int buf = 0;
    ssize_t size = read(fd, &buf, sizeof(buf));
    assert(size >= 0);
    if(buf == FIFO_DISPOSE_DATA){
   
   
        int cnt = 5;
        while(cnt > 0)
        {
   
   
            printf("数据库正在处理数据中..............(请等待<%d>秒钟)\n", cnt--);
            sleep(1);
        }
    }    
    if(size == -1){
   
   
        std::cout << "dispose_data进程读取错误";
        exit(0);
    }
    return 0;
}

ManageSystem.cc

#include "NamepipePool.hpp"

int main()
{
   
   
    int fd = open(fifo_manage_system.c_str(), O_RDWR);
    assert(fd != -1);
    int buf = 0;
    while(true)
    {
   
   
        ssize_t size = read(fd, &buf, sizeof(buf));
        assert(size >= 0);
        if(buf == FIFO_MANAGE_SYSTEM){
   
   
            int cnt = 5;
            while(cnt > 0)
            {
   
   
                printf("系统正在管理各个子系统中..............(请等待<%d>秒钟)\n", cnt--);
                sleep(1);
            }
            break;
        } 
        if(size == -1){
   
   
            std::cout << "manage_system进程读取错误";
            exit(0);
        } 
    }


    return 0;
}

NamepipePool.hpp

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

//管道名
std::string fifo_net_request = "./fifo_net_request";
std::string fifo_dispose_data = "./fifo_dispose_data";
std::string fifo_manage_system = "./fifo_manage_system";
#define MODE 0664
#define FIFO_NET_REQUEST 1
#define FIFO_DISPOSE_DATA 2
#define FIFO_MANAGE_SYSTEM 3
#define PROCESS_NUM 3


class fifo
{
   
   
public:
    fifo(const int& fd, std::string& name)
        :_fifo_fd(fd)
        ,_fifo_name(name)
    {
   
   }
    fifo(const fifo& x)
    {
   
   
        _fifo_fd = x._fifo_fd;
        _fifo_name = x._fifo_name;
    }
    ~fifo(){
   
   }
public:
    int _fifo_fd;
    std::string _fifo_name;
};

class Manage_fifo
{
   
   
public:
    Manage_fifo(){
   
   }

    Manage_fifo(const fifo fd1, const fifo fd2, const fifo fd3)
    {
   
   
        _fifos.push_back(fd1);
        _fifos.push_back(fd2);
        _fifos.push_back(fd3);
    }

    ~Manage_fifo(){
   
   }
public:
    std::vector<fifo> _fifos;
};

NetRequest.cc

#include "NamepipePool.hpp"

int main()
{
   
   
    int fd = open(fifo_net_request.c_str(), O_RDWR);
    assert(fd != -1);
    int buf = 0;
    ssize_t size = read(fd, &buf, sizeof(buf));
    assert(size > 0);
    if(buf == FIFO_NET_REQUEST){
   
   
        int cnt = 5;
        while(cnt > 0)
        {
   
   
            printf("网络正在请求中..............(请等待<%d>秒钟)\n", cnt--);
            sleep(1);
        }
    }    
    if(size == -1){
   
   
        std::cout << "net_request进程读取错误";
        exit(0);
    }
    return 0;
}

Makefile

.PHONY:all
all:ControlCenter ManageSystem NetRequest DisposeData

ControlCenter:ControlCenter.cc
    g++ -o {
   
   mathJaxContainer[0]}^ -std=c++11 -g
ManageSystem:ManageSystem.cc
    g++ -o {
   
   mathJaxContainer[1]}^ -std=c++11
NetRequest:NetRequest.cc
    g++ -o {
   
   mathJaxContainer[2]}^ -std=c++11 -g
DisposeData:DisposeData.cc
    g++ -o {
   
   mathJaxContainer[3]}^ -std=c++11

.PHONY:clean
clean:
    rm -f ControlCenter ManageSystem NetRequest DisposeData 
    rm -f fifo_*

Linux进程间通信【匿名管道和命名管道】,到这里就介绍结束了,本篇文章对你由帮助的话,期待大佬们的三连,你们的支持是我最大的动力!

文章有写的不足或是错误的地方,欢迎评论或私信指出,我会在第一时间改正

目录
相关文章
|
8月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
305 67
|
7月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
223 16
|
7月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
143 20
|
6月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
131 0
|
6月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
203 0
|
6月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
133 0
|
6月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
184 0
|
9月前
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。
|
9月前
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
708 5
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能