进程间通信-管道

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

每个进程是相互独立的(代码和数据),但是往往许多工作需要俩个进程之间协同工作,所以学会进程通信很重要。今天刚刚学会管道这种模式,所以总结下做个笔记。


一、管道原理

1、进程通信目的

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

2、让不同的进程看到同一份资源成为了进程通信的关键

  • 如果能让进程1与进程2都能够操作磁盘中的文件,就能够解决进程之间的通信问题

在这里插入图片描述

  • 让不同进程看到同一文件
    在这里插入图片描述

3、管道特点

  • 单向的(半双工)
  • 传输的是数据

二、匿名管道(pipe)

1、pipe函数

在这里插入图片描述

2.pipe应用与父子进程之间通信

#include <iostream>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>


using namespace std;

int main()
{
    // 创建管道
    // 定义一个数组来接受管道的读写描述符
    int pipefd[2] = {0};
    if (pipe(pipefd) != 0)
    {
        // 创建管道失败
        cerr << "pipe erro" << endl;
        return 1;
    }
    // 创建子进程
    pid_t id = fork();
    if (id < 0)
    {
        cerr << "fork erro" << endl;
        return 2;
    }
    else if (id == 0)
    {
        // 子进程
        // 子进程完成读,关闭写操作
        close(pipefd[1]);
        // 读入内容
        #define NUM 1024
        char buf[NUM];
        while(true)
        {
            //将缓冲区清零
            memset(buf,0,sizeof(buf));
            size_t s = read(pipefd[0],buf,sizeof(buf)-1);
            if(s > 0)
            {
                //读到了数据
                buf[s] = '\0';
                cout<<"子进程接受到的数据是:~"<<buf<<endl;

            }
            else if(s == 0)
            {

                cout<<"父进程写完了,我也退出了"<<endl;
                break;
            }

        }
        // 关闭读操作
        close(pipefd[0]);
        exit(0);
    }
    else
    {
        // 父进程
        // 父进程完成写,关闭读操作
        close(pipefd[0]);
        // 写入内容
        const char* msg = "子进程,我是父进程,这次发送的编号是:";
        int cnt = 0;
        while (cnt<5)
        {
            char sendbuf[1024];
            sprintf(sendbuf,"%s:%d",msg,cnt);
            write(pipefd[1],sendbuf,strlen(sendbuf));
            cnt++;
            sleep(1);
        }
        
        // 关闭写操作
        close(pipefd[1]);
    }
    //阻塞等待子进程退出、回收子进程
    pid_t res = waitpid(id,nullptr,0);
    if(res > 0)
    {

        cout<<"等待子进程退出成功"<<endl;
    }
    
}

运行结果:
在这里插入图片描述

3、pipe应用于父进程控制子进程

#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cassert>

using namespace std;
typedef void (*functor)();
vector<functor> functors;
void f1()
{
    cout << "这是一个处理日志的任务,执行的进程 ID [" << getpid() << "]"
         << "执行时间是 [" << time(nullptr) << "]\n"
         << endl;
}
void f2()
{
    cout << "这是一个数据的任务,执行的进程 ID [" << getpid() << "]"
         << "执行时间是 [" << time(nullptr) << "]\n"
         << endl;
}
void f3()
{
    cout << "这是一个处理网络的任务,执行的进程 ID [" << getpid() << "]"
         << "执行时间是 [" << time(nullptr) << "]\n"
         << endl;
}
//将任务加载到数组中
void loadFunctor()
{
    functors.push_back(f1);
    functors.push_back(f2);
    functors.push_back(f3);
}

int main()
{
    //加载任务
    loadFunctor();
    // 创建管道
    int pipefd[2] = {0};
    if (pipe(pipefd) != 0)
    {
        // 创建管道失败
        cerr << "pipe erro" << endl;
        return 1;
    }

    // 创建管道成功、创建父子进程
    pid_t id = fork();
    if (id < 0)
    {
        // 创建子进程失败
        cerr << "fork erro" << endl;
        return 2;
    }
    else if (id == 0)
    {
        // 子进程
        // 子进程读操作、关闭写操作
        close(pipefd[1]);
        // 读任务编码
        while(true)
        {
            uint32_t operatorType = 0;
            size_t s = read(pipefd[0],&operatorType,sizeof(uint32_t));
            if(s == 0)
            {
                //表示父进程不再写了
                cout<<"父进程退出了,我也退出了!"<<endl;
                break;
            }
            assert(s == sizeof(uint32_t));
            (void)s;
            if(operatorType < functors.size())
            {
                functors[operatorType]();
            }
            else
            {
                cerr<<"读取错误"<<endl;
            }

        }
        close(pipefd[0]);
        exit(0);
    }
    else if (id > 0)
    {
        //种子
        srand((long long)time(nullptr));
        // 父进程
        // 父进程进行写操作、关闭读操作
        close(pipefd[0]);
        // 发送任务编号
        int num = functors.size();
        int cnt = 10;
        while (cnt--)
        {
            //形成任务码
            uint32_t commandCode = rand() % num;
            //发送任务码
            write(pipefd[1],&commandCode,sizeof(uint32_t));
            sleep(1);
        }

        close(pipefd[1]);
        // 当父进程处理完之后,等待子进程结束进行回收
        pid_t res = waitpid(id, nullptr, 0);
        if (res)
        {
            cout << "等待子进程退出" << endl;
        }
    }
}

运行结果:
在这里插入图片描述

4、pipe父进程控制一批子进程

#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cassert>

using namespace std;
typedef void (*functor)();
// 装入函数
vector<functor> functors;
// 定义一个pair,存放子进程的ID与文件描述符
typedef pair<int32_t, int32_t> elem;
// 定义子进程数量
int processNum = 5;
void f1()
{
    cout << "这是一个处理日志的任务"
         << "执行时间是 [" << time(nullptr) << "]\n"
         << endl;
}
void f2()
{
    cout << "这是一个数据的任务"
         << "执行时间是 [" << time(nullptr) << "]\n"
         << endl;
}
void f3()
{
    cout << "这是一个处理网络的任务"
         << "执行时间是 [" << time(nullptr) << "]\n"
         << endl;
}
// 将任务加载到数组中
void loadFunctor()
{
    functors.push_back(f1);
    functors.push_back(f2);
    functors.push_back(f3);
}

void work(int blockFd)
{
    while (true)
    {
        uint32_t operatorData = 0;
        size_t s = read(blockFd, &operatorData, sizeof(uint32_t));
        if (s == 0)
        {
            cout << "父进程不写了,我要退出了" << endl;
        }
        assert(s == sizeof(uint32_t));
        (void)s;
        if (operatorData < functors.size())
            functors[operatorData]();
    }
}

void blanceSendTask(const vector<elem> processFds)
{
    // 种子
    srand((long long)time(nullptr));
    while (true)
    {
        sleep(1);
        // 随机选择一个进程
        uint32_t pick = rand() % processFds.size();
        // 随机产生一个任务
        uint32_t task = rand() % functors.size();
        // 将任务分发给进程
        write(pick, &task, sizeof(uint32_t));
    }
}
int main()
{
    // 加载任务
    loadFunctor();
    vector<elem> assignMap;
    for (int i = 0; i < processNum; i++)
    {
        // 创建管道
        int pipefd[2] = {0};
        if (pipe(pipefd) != 0)
        {
            cerr << "pipe erro" << endl;
            return 2;
        }
        // 创建进程
        pid_t id = fork();
        if (id < 0)
        {
            cerr << "fork erro" << endl;
            return 1;
        }
        else if (id == 0)
        {
            // 子进程
            // 子进程需要关闭写操作,并读取父进程发来的命令
            close(pipefd[1]);
            work(pipefd[0]);
            // 执行完关闭操作
            close(pipefd[0]);
            exit(0);
        }
        else if (id > 0)
        {
            // 父进程
            // 父进程需要关闭读操作,并收集子进程的id,与写文件描述符
            close(pipefd[0]);
            elem e(id, pipefd[1]);
            assignMap.push_back(e);
        }
    }
    cout << "创建所有子进程成功" << endl;
    blanceSendTask(assignMap);
    // 等待子进程结束,回收子进程

    for (int i = 0; i < processNum; i++)
    {
        if (waitpid(assignMap[i].first, nullptr, 0) > 0)
        {
            cout << "等待成功,回收子进程,子进程是:" << assignMap[i].first << endl;
            // 关闭写操作
            close(assignMap[i].second);
        }
    }
}

运行结果:
在这里插入图片描述

二、命名管道(mkfifo)

1、命名管道

如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。

2、命令行创建命名管道

在这里插入图片描述

3、代码创建命令行

客户端:

#include "comm.h"

using namespace std;
int main()
{
   int pipeFd = open(IPC_PATH, O_WRONLY);
    if(pipeFd < 0)
    {
        cerr << "open error " << endl;
        return 1;
    }

#define NUM 1024
    char line[NUM];
    while(true)
    {
        printf("请输入你的消息# ");
        fflush(stdout);
        memset(line, 0, sizeof(line));
        // fgets -> C -> line结尾自动添加\0
        if(fgets(line, sizeof(line), stdin) != nullptr)
        {
            //abcd\n\0
            line[strlen(line) - 1] = '\0';
            write(pipeFd, line, strlen(line));
        }
        else
        {
            break;
        }
    }
    close(pipeFd);
    return 0;  
}

服务端:

#include "comm.h"

using namespace std;

int main()
{
    umask(0);
    //创建命名管道
    if(mkfifo(IPC_PATH,0600) != 0)
    {
        cerr<<"mkfifo error"<<endl;
        return 1;
    }

    //读管道
    int pipefd = open(IPC_PATH,O_RDONLY);
    if(pipefd < 0)
    {
        cerr<<"open fifo error"<<endl;
        return 2;
    }
    //打开管道成功
    #define NUM 1024
    char buffer[NUM];
    while(true)
    {
        size_t s = read(pipefd,buffer,sizeof(buffer)-1);
        if(s == 0)
        {
            cout<<"客户推出了,我也退出"<<endl;
            break;
        }
        else if(s > 0)
        {
            buffer[s] = '\0';
            cout<<"客户端->服务端#"<<buffer<<endl;
        }
    }
    close(pipefd);
    unlink(IPC_PATH);
    return 0;
}

运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值