进程间通信

一、进程间通信的目的:

进程间通信的本质:是先让不同的进程看到同一份资源

• 数据传输:⼀个进程需要将它的数据发送给另⼀个进程 • 资源共享:多个进程之间共享同样的资源。

• 通知事件:⼀个进程需要向另⼀个或⼀组进程发送消息,通知它(它们)发生了某种事件(如进 程终止时要通知父进程)。

• 进程控制:有些进程希望完全控制另⼀个进程的执行(如Debug进程),此时控制进程希望能够 拦截另⼀个进程的所有陷入和异常,并能够及时知道它的状态改变

二、管道:

1.原理:父进程创建文件描述符表,表里的指针指向结构体struct file,里面的属性指向inode和缓冲区等,创建子进程,子进程同时会拷贝文件描述符表,所以子进程和父进程指向同一个结构体,也就可以指向同一个文件,首先实现了不同进程看到同一份资源。至此,让不同的进程看到同一份资源的struct file和缓冲区这一部分叫做管道。但是这块管道是属于通信的范畴,和磁盘文件没有关系,所以原理上要修改,不能与文件挂钩。

管道两端分别同时进行读和写操作,同时返回两个文件描述符表。可以单项通信。

由于是操作系统自己写的,属于内存级别,因此打开管道的时候不需要路径,也就是它没有名字。匿名管道。子进程继承了父进程的文件描述符表,所以两个进程指向的管道是同一个,在没有名字的情况下可以保证是一条管道。

代码实现:

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys.wait.h>


int main()
{
    //1.创建管道
    int fds[2]={0};//创建两个文件描述符,fd[0]通常表示读端 fd[1]通常表示写端
    int n=pipe(fds);//创建管道

    if(n<0)//成功返回0失败返回1
    { 
        std::cout<<"pipe error"<<std::endl;
        return 1;
    }  

    std::cout<<"fds[0]: "<<fds[0]<<std::endl;//这两个文件描述符的值一定是3和4
    std::cout<<"fds[1]: "<<fds[1]<<std::endl;


    return 0;
}
cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys.wait.h>

void ChildWrite(int wfd)
{
    char buffer[1024];
    int cnt=0;
    while(true)
    {
        snprintf(buffer,sizeof(buffer),"我是一个子进程,pid:%d,cnt:%d",getpid(),cnt++);
    }
}
void FatherRead(int rfd)
{

}

int main()
{
    //1.创建管道
    int fds[2]={0};//创建两个文件描述符,fd[0]通常表示读端 fd[1]通常表示写端
    int n=pipe(fds);//创建管道

    if(n<0)//成功返回0失败返回1
    { 
        std::cout<<"pipe error"<<std::endl;
        return 1;
    }  

    std::cout<<"fds[0]: "<<fds[0]<<std::endl;//这两个文件描述符的值一定是3和4
    std::cout<<"fds[1]: "<<fds[1]<<std::endl;

    //2.创建子进程
    pid_t id=fork();
    if(id==0)
    {
        //3.关闭不需要的读写段,形成通信通道
        //父 -> r  子 -> w
        close(fd[0]);//子进程去写要关闭读端
        close(fd[1]);
        exit(0);
    }

    //3.关闭不需要的读写段,形成通信通道
    //父 -> r  子 -> w
    close(fd[1]);//子进程去写要关闭读端

    waitpid(id,nullptr,0);
    close(fd[0]);
    exit(0);

    return 0;
}
cpp 复制代码
void ChildWrite(int wfd)
{
    char buffer[1024];
    int cnt=0;
    while(true)
    {
        snprintf(buffer,sizeof(buffer),"我是一个子进程,pid:%d,cnt:%d",getpid(),cnt++);
        write(wfd,buffer,strlen(buffer));
        sleep(1);
    }
}
void FatherRead(int rfd)
{
    char buffer[1024];
    while(true)
    {
        buffer[0]={0};
        ssize_t n=read(rfd,buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]=0;
            std::cout<<"child say:"<<buffer<<std::endl;
        }
    }
}

管道也属于文件系统,向文件写字符串不需要写\0。所以读的时候就要自己加\0

3.特性:

(1)匿名管道只能用来进行有血缘关系的进程之间的传输。

(2)管道文件具有同步机制。

(3)管道面向字节流。

(4)管道单向通信。

(5)文件生命周期随进程。

4.管道的4种通信情况

1.写慢读快:读端等待写端读端不工作进程阻塞

2.写快读慢:写满管道时写就要阻塞等到读端的进程

3.写关闭,读继续:read会读到返回值0表示文件结尾

4.读关闭,写继续:没有通信的意义,操作系统发送异常信号直接关闭写进程

三、基于匿名管道---进程池:

在父进程下创建五个管道,有很多写端,要对自己创建的通道管理,再在fork创建五个子进程与管道一一对应,通过不同的管道向不同的子进程写入。如果父进程不想写,所以的进程都会阻塞在read这里等待父进程写消息。写一次消息子进程跑一次,父进程通过管道来暂停或唤醒对应的子进程,规定父进程给子进程写入的数据是整数值int code,每次读四字节。可以将数字规定为任务码,读到哪个数字就去完成某个任务,这种把子进程提前创建一批就叫进程池。

代码实现:

cpp 复制代码
#ifndef __PROCESS_POOL_HPP__
#define __PROCESS_POOL_HPP__

#include <iostream>
#include <vector>
#include <unistd.h>
#include <cstdlib>
#include "Task.hpp"
#include <sys/wait.h>

// 先描述
class Channel
{
public:
    Channel(int fd, pid_t id) : _wfd(fd), _subid(id)
    {
        _name = "channel-" + std::to_string(_wfd) + "-" + std::to_string(_subid); // 将整形转成字符串
    }
    void Send(int code)
    {
        int n = write(_wfd, &code, sizeof(code)); // 往管道里写
        (void)n;                                  // 强转一下n绕过编译器对未使用变量的违法检查
    }
    int Fd()
    {
        return _wfd;
    }
    pid_t Subid()
    {
        return _subid;
    }
    std::string Name()
    {
        return _name;
    }
    void Close()
    {
        close(_wfd);
    }
    void Wait()
    {
        pid_t rid = waitpid(_subid, nullptr, 0);
        (void)rid;
    }
    ~Channel()
    {
    }

private:
    int _wfd;     // 文件描述符
    pid_t _subid; // 当前Channel对应的子进程
    std::string _name;
};
// 再组织
class ChannelManager
{
public:
    ChannelManager() : _next(0)
    {
    }
    void Insert(int wfd, pid_t subid)
    {
        _channels.emplace_back(wfd, subid);
        // Channel c(wfd, subid);
        // _channels.push_back(c);
    }
    Channel &Select()
    {
        auto &c = _channels[_next];
        _next++;
        _next %= _channels.size(); // 防止_next越界
        return c;
    }
    void PrintChannel()
    {
        for (auto &channel : _channels)
        {
            std::cout << channel.Name() << std::endl;
        }
    }
    void StopSubProcess()
    {
        for (auto &channel : _channels)
        {
            channel.Close();
            std::cout << "关闭" << channel.Name() << std::endl;
        }
    }
    void WaitSubProcess() // 等待子进程退出
    {
        for (auto &channel : _channels)
        {
            channel.Wait();
            std::cout << "回收" << channel.Name() << std::endl;
        }
    }
    ~ChannelManager()
    {
    }

private:
    std::vector<Channel> _channels; // 用vector管理起来
    int _next;                      // 用于父进程选择子进程
};

int gdefaultnum = 5;

// 进程池
class ProcessPool
{
public:
    ProcessPool(int num) : _process_num(num)
    {
        // 注册任务
        _tm.Register(PrintLog);
        _tm.Register(DownLoad);
        _tm.Register(UpLoad);
    }
    void Work(int rfd)
    {
        while (true)
        {
            // 子进程要等待父进程的投喂
            int code = 0;
            ssize_t n = read(rfd, &code, sizeof(code));
            if (n > 0)
            {
                if (n != sizeof(code)) // 读取不完整继续读直到读取规范
                {
                    continue;
                }
                std::cout << "子进程[" << getpid() << "]收到一个任务码" << code << std::endl;
                _tm.Execute(code); // 执行任务
            }
            else if (n == 0)
            {
                std::cout << "子进程退出" << std::endl;
                break;
            }
            else
            {
                std::cout << "读取错误" << std::endl;
                break;
            }
        }
    }
    bool Start()
    {
        // 维护进程池的结构关系:
        for (int i = 0; i < _process_num; i++) // for循环里创建的管道都是临时空间,所以要管理
        {
            // 创建管道
            int pipefd[2] = {0};
            int n = pipe(pipefd);
            if (n < 0) // 管道创建失败
                return false;

            // 2.创建子进程
            pid_t subid = fork();
            if (subid < 0)
                return false;
            else if (subid == 0)
            {
                // 子进程
                // 关闭不需要的文件描述符
                close(pipefd[1]);
                Work(pipefd[0]); // 工作
                close(pipefd[0]);
                exit(0); // 子进程工作做完就退出
            }
            else
            {
                // 父进程
                // 关闭不需要的文件描述符
                close(pipefd[0]);
                _cm.Insert(pipefd[1], subid); // 没创建一个管道就在vector里加一个
            }
        }

        return true;
    }
    void Debug()
    {
        _cm.PrintChannel();
    }
    void Run()
    {
        // 1.选择一个任务
        int taskcode = _tm.Code();
        // 2.负载均衡的选择一个子进程完成任务
        auto &c = _cm.Select();
        std::cout << "选择一个子进程" << c.Name() << std::endl;
        // 2.给进程发送任务
        c.Send(taskcode);
        std::cout << "发送一个任务码" << taskcode << std::endl;
    }
    void Stop()
    {
        // 关闭父进程所以的wfd
        _cm.StopSubProcess();
        // 子进程进入僵尸要回收
        _cm.WaitSubProcess();
    }
    ~ProcessPool()
    {
    }

private:
    ChannelManager _cm; // 里面包含信道
    int _process_num;   // 创建进程池的个数
    TaskManager _tm;    // 管理任务
};

#endif
cpp 复制代码
#pragma once

#include <iostream>
#include <vector>
#include <time.h>

typedef void (*task_t)();

// 要执行的任务
void PrintLog()
{
    std::cout << "我是一个打印日志任务" << std::endl;
}

void DownLoad()
{
    std::cout << "我是一个下载任务" << std::endl;
}

void UpLoad()
{
    std::cout << "我是一个上传任务" << std::endl;
}

class TaskManager
{
public:
    TaskManager()
    {
        srand(time(nullptr));
    }
    void Register(task_t t) // 注册任务
    {
        _tasks.push_back(t);
    }
    int Code()
    {
        return rand() % _tasks.size();
    }
    void Execute(int code)
    {
        if (code > 0 && code < _tasks.size())
        {
            _tasks[code]();
        }
    }
    ~TaskManager()
    {
    }

private:
    std::vector<task_t> _tasks;
};
cpp 复制代码
#include "Process.hpp"

int main()
{
    // 创建进程池对象
    ProcessPool pp(gdefaultnum);

    // 启动进程池
    pp.Start();

    // 派发任务
    int cnt = 10;
    while (cnt--)
    {
        // 1.选择一个信道之后发信息给子进程
        pp.Run(); // 把任务码交给进程池再由它转发给子进程
        sleep(1);
    }

    // 回收,结束进程池
    pp.Stop();

    return 0;
}
相关推荐
QQ7198725782 小时前
Linux【4】:FTP服务搭建
linux·运维·服务器
习惯就好zz2 小时前
如何解包 Android boot.img 并检查 UART 是否启用
android·linux·dtc·3588·dts·解包·dtb
西格电力科技2 小时前
为何要配光伏储能协调控制服务器?核心价值与应用必要性
大数据·运维·服务器·人工智能·架构·能源
zl_dfq2 小时前
Linux 之 【进程替换】(execl、execlp、execle、execv、execvp、execve)
linux
乌蒙山连着山外山2 小时前
linux中查询多个匹配字段
java·linux·服务器
乘凉~2 小时前
在Ubuntu上部署并使用xianyu-auto-reply
linux·运维·ubuntu
恒创科技HK2 小时前
香港服务器应该选择多大带宽?同时能承载多少用户的访问
运维·服务器
wanhengidc2 小时前
云手机的扩展性怎么样?
运维·服务器·科技·安全·游戏·智能手机
中屹指纹浏览器2 小时前
指纹浏览器与代理 IP 的跨协议栈协同优化技术
服务器·网络·经验分享·笔记·媒体