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

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

相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
目录
相关文章
|
22天前
|
消息中间件 Linux API
Linux c/c++之IPC进程间通信
这篇文章详细介绍了Linux下C/C++进程间通信(IPC)的三种主要技术:共享内存、消息队列和信号量,包括它们的编程模型、API函数原型、优势与缺点,并通过示例代码展示了它们的创建、使用和管理方法。
20 0
Linux c/c++之IPC进程间通信
|
22天前
|
Linux C++
Linux c/c++进程间通信(1)
这篇文章介绍了Linux下C/C++进程间通信的几种方式,包括普通文件、文件映射虚拟内存、管道通信(FIFO),并提供了示例代码和标准输入输出设备的应用。
17 0
Linux c/c++进程间通信(1)
|
2月前
|
消息中间件 Unix Linux
C语言 多进程编程(二)管道
本文详细介绍了Linux下的进程间通信(IPC),重点讨论了管道通信机制。首先,文章概述了进程间通信的基本概念及重要性,并列举了几种常见的IPC方式。接着深入探讨了管道通信,包括无名管道(匿名管道)和有名管道(命名管道)。无名管道主要用于父子进程间的单向通信,有名管道则可用于任意进程间的通信。文中提供了丰富的示例代码,展示了如何使用`pipe()`和`mkfifo()`函数创建管道,并通过实例演示了如何利用管道进行进程间的消息传递。此外,还分析了管道的特点、优缺点以及如何通过`errno`判断管道是否存在,帮助读者更好地理解和应用管道通信技术。
|
2月前
|
SQL 网络协议 数据库连接
已解决:连接SqlServer出现 provider: Shared Memory Provider, error: 0 - 管道的另一端上无任何进程【C#连接SqlServer踩坑记录】
本文介绍了解决连接SqlServer时出现“provider: Shared Memory Provider, error: 0 - 管道的另一端上无任何进程”错误的步骤,包括更改服务器验证模式、修改sa用户设置、启用TCP/IP协议,以及检查数据库连接语句中的实例名是否正确。此外,还解释了实例名mssqlserver和sqlserver之间的区别,包括它们在默认设置、功能和用途上的差异。
|
3月前
|
开发者 API Windows
从怀旧到革新:看WinForms如何在保持向后兼容性的前提下,借助.NET新平台的力量实现自我进化与应用现代化,让经典桌面应用焕发第二春——我们的WinForms应用转型之路深度剖析
【8月更文挑战第31天】在Windows桌面应用开发中,Windows Forms(WinForms)依然是许多开发者的首选。尽管.NET Framework已演进至.NET 5 及更高版本,WinForms 仍作为核心组件保留,支持现有代码库的同时引入新特性。开发者可将项目迁移至.NET Core,享受性能提升和跨平台能力。迁移时需注意API变更,确保应用平稳过渡。通过自定义样式或第三方控件库,还可增强视觉效果。结合.NET新功能,WinForms 应用不仅能延续既有投资,还能焕发新生。 示例代码展示了如何在.NET Core中创建包含按钮和标签的基本窗口,实现简单的用户交互。
66 0
|
Linux Shell C++
Linux进程间通信——使用匿名管道
在前面,介绍了一种进程间的通信方式:使用信号,我们创建通知事件,并通过它引起响应,但传递的信息只是一个信号值。这里将介绍另一种进程间通信的方式——匿名管道,通过它进程间可以交换更多有用的数据。   一、什么是管道 如果你使用过Linux的命令,那么对于管道这个名词你一定不会感觉到陌生,因为我们通常通过符号“|"来使用管道,但是管理的真正定义是什么呢?管道是一个进程连接数据流到另一个进程的通道,它通常是用作把一个进程的输出通过管道连接到另一个进程的输入。
1194 0
|
8天前
|
运维 安全 Linux
Linux中传输文件文件夹的10个scp命令
【10月更文挑战第18天】本文详细介绍了10种利用scp命令在Linux系统中进行文件传输的方法,涵盖基础文件传输、使用密钥认证、复制整个目录、从远程主机复制文件、同时传输多个文件和目录、保持文件权限、跨多台远程主机传输、指定端口及显示传输进度等场景,旨在帮助用户在不同情况下高效安全地完成文件传输任务。
83 5
|
8天前
|
Linux
Linux系统之expr命令的基本使用
【10月更文挑战第18天】Linux系统之expr命令的基本使用
35 4
|
5天前
|
运维 监控 网络协议
|
6天前
|
监控 Linux Shell