匿名管道 Linux

news2024/9/30 10:40:54

管道

首先自己要用用户层缓冲区,还得把用户层缓冲区拷贝到管道里,(从键盘里输入数据到用户层缓冲区里面),然后用户层缓冲区通过系统调用(write)写到管道里,然后再通过read系统调用,被对方(读端)读取,就要从管道拷贝到读端,然后再显示到显示器上。

pipe创建一个管道

pipe的介绍

1完成这件事:

看图分析

运行结果

#include<iostream>
#include<unistd.h>
using namespace std;
int main()
{
    //创建管道
    //先创建一个pipefd数组
    int pipefd[2];
    //用n接受一下,判断是否成功
    int n = pipe(pipefd);
    if(n<0) return 1;//创建失败了

    //创建成功
    //测试一下文件描述符是3和4
    cout<<"pipefd[0]:"<<pipefd[0]<<"pipefd[1]:"<<pipefd[1]<<endl;
    

    return 0;
}

2完成这件事:

创建一个子进程

 pid_t id = fork();
    if(id < 0)return 2;//创建失败
    if(id == 0)//创建成功
    {
        //子进程

    }
    //父进程

让子进程写入,父进程读取

要想让子进程进程写,就需要在进程中关闭读端

if(id == 0)//创建成功
{
     //子进程
     close(pipefd[0]);
}

同理

//父进程
close(pipefd[1]);

都用完结束后,可以都关掉

    if(id == 0)//创建成功
    {
        //子进程
        close(pipefd[0]);
        //.....
        close(pipefd[1]);
    }
    //父进程
    close(pipefd[1]);
    //.....
    close(pipefd[0]);

IPC code,写通信代码

3这件事也完成了:

结构就有了

然后在pipefd[1]这个管道里写,定义一个Writer函数

    if(id == 0)//创建成功
    {
        //子进程
        close(pipefd[0]);
        //.....IPC code,写通信代码
        //在pipefd[1]这个管道里写
        Writer(pipefd[1]);


        close(pipefd[1]);

        exit(0);//正常退出
    }

同理父进程的        

    //父进程
    close(pipefd[1]);
    //.....IPC code,写通信代码
    //在pipefd[0]这个管道里写
    Reader(pipefd[0]);


    close(pipefd[0]);

//子进程
void Writer(int wfd)
{

}
//父进程
void Reader(int rfd)
{

}

Writer

//子进程
void Writer(int wfd)
{
    string s = "hello,I am child";
    pid_t self = getpid();
    int number = 0;

    char buffer[10];
    while(true)
    {
        buffer[0] = 0;//字符串清空,只是为了提醒阅读代码的人,我把这个数组当字符串了

    }
}

用到snprintf
介绍

将s和self和number放进buffer

  char buffer[100];
    while(true)
    {
        buffer[0] = 0;//字符串清空,只是为了提醒阅读代码的人,我把这个数组当字符串了
        snprintf(buffer,sizeof(buffer),"%s    pid:%d\n",s.c_str(),self);
        cout<< buffer <<endl;
        sleep(1);
    };

用cout打印测试一下,打印成功说明写入buffer成功了

等待进程少不了,子进程exit后需要回收

 //父进程
    close(pipefd[1]);
    //.....IPC code,写通信代码
    //在pipefd[0]这个管道里写
    Reader(pipefd[0]);

    //等待进程缺少不了
    pid_t rid = waitpid(id,nullptr,0);
    if(rid < 0) return 3;//等待失败了

    close(pipefd[0]);

如何把消息发送/写入给父进程

用到了write

用write写入管道(管道也是文件),用strlen,不用+1,不用管\0,因为C语言规定\0结尾,和文件没有关系,wfd写入管道

//子进程
void Writer(int wfd)
{
    string s = "hello,I am child";
    pid_t self = getpid();
    int number = 0;
    char buffer[100];
    while(true)
    {
        buffer[0] = 0;//字符串清空,只是为了提醒阅读代码的人,我把这个数组当字符串了
        snprintf(buffer,sizeof(buffer),"%s    pid:%d  %d\n",s.c_str(),self,number++);
        //用write写入管道(管道也是文件),用strlen,不用+1,不用管\0,因为C语言规定\0结尾,和文件没有关系,wfd写入管道
        write(wfd,buffer,strlen(buffer));
        //cout<< buffer <<endl;
        sleep(1);
    };
}

父进程该怎么读取呢

用到了read,fd是文件描述符,从特定的文件描述符里读取,放在这个buf里,buf的长度是count

这里就需要考虑到\0,因为buffer中需要\0

//父进程
void Reader(int rfd)
{
    char buffer[100];
    while(true)
    {
        buffer[0] = 0;
                                    //用sizeof是为了留个空间放\0
        ssize_t n = read(rfd, buffer, sizeof(buffer));//sizeof!=strlen
        if(n > 0)
        {
            //添加\0,因为要放在buffer数组中读取
            buffer[n]=0;
            cout << "father get a message[" << getpid() <<"]"<< buffer <<endl;
        }
    }
}

运行结果

也会发现:为什么子进程sleep,父进程不sleep,父进程还是会跟着子进程sleep,因为父子进程是要协同的

管道本质

通信是为了更好的发送变化的数据,管道本质上是文件

所以必须要用到系统调用接口来访问管道,其是由系统管理,read和write

,操作系统相当于中介 

结论:管道的特征:

1:具有血缘关系的进程进行进程间通信

2:管道只能单向通信

3:父子进程是会进程协同的,同步与互斥的--保护管道文件的数据安全

4:管道是面向字节流的

5:管道是基于文件的,而文件的生命周期是随进程的

再测试,把子进程sleep去掉,就是让子进程写快一点,父进程sleep几秒,就是让父进程读慢一点,看有什么现象

 管道的四种情况

测试管道大小

把c一直往管道里写,把父进程中休眠50秒

结果差不多64kb

写端退了,测试结果

结果是:

读端正常读,写端关闭,读端就会读到0,表明读到了文件(pipe)结尾,不会被阻塞

read读取成功会返回读到的字符个数,读到结尾返回0

读到结尾父进程也就可以停止读取了,break后去把僵尸的子进程回收

break到这里

最后子进程会被waitpid回收

测试子进程一直写,父进程读一会就退出

定义一个cnt控制退出的时间

这里也要修改一下,加个sleep(5),观察,close提前关闭

结果:通过13号信号杀死 

管道到的应用场景

都会变成一个进程

写一个进程池(pipe_use)

首先创建好文件

创建5个进程

channel通道的意思

cmdfd文件描述符

slaverid代表哪个子进程

把它放进vector容器里

思路步骤

管道创建

void(n),假装使用一下,要不然编译不过

创建父子进程

父进程写,子进程读

子进程要读取,就要关闭自己的写端,父进程同理

子进程中的任务

子进程pid有了管道也有了,就差在父进程添加字段了

先更改一下,在class里构造一下

添加字段

测试一下:结果:文件描述符0,1,2是默认打开,3是从管道里读,4是写入管道

把初始化改造成函数

debug测试函数,纯输入函数

第二步开始控制进程了(想让子进程做什么)

这里打印的rfd都是3,正常吗,文件描述符是可以被子进程继承的

父进程对应的写端拿到的是4-8,子进程拿到的读端fd是3

改变一下,直接从键盘(0号描述符)里读,不从管道(3)里读了,就没有管道的概念了,slaver就不用传参了,父进程通过管道写,子进程通过标准输入读

用到了dup2,将从pipefd[0]中读变成从0开始读

想让父进程固定的向管道里写入指定大小字节的内容,必须读取四个字节,四个字节四个字节的写和读,这里的管道64kb

必须读取四个字节

如果父进程不给子进程发送数据呢?阻塞等待!

开始控制子进程

生成一个随机数种子

可以随机选择任务和选择进程

cmd是任务码,测试一下,父进程控制子进程,父进程发送给子进程(通过cmdcode连续)

在Task.hpp里

要用到函数指针

main中的任务了就属于

再把任务装载进来

输出型参数用*

现在开始选择任务和进程

再把main中的任务弄成全局的

进行判断一下

测试 ,comcode和任创建的任务一致

这里的write是父进程进行写入,向子进程发送,子进程不得闲,先写到管道里,等得闲了再读

也可以轮询选择,定义一个计数器,++弄,再%等

整理一下控制代码,这里是输入型参数,只需要读

这样就可以轮询方式选择进程了,不用随机了

结果

清理收尾

思路:把所有文件的描述符都关掉

等待方式设置为0 

read返回0,就是失败了,然后slaver就会调完

结束完就会exit直接退出

打印下更好显示

关闭文件描述符后sleep(10)秒,

然后这10个子进程一瞬间都应该break,然后最后到exit直接就退了,10秒结束后,父进程再回收他       

测试时不弄死循环,用cnt,5秒后自动结束控制,正常退出流程

测试结果

手动控制一下

定义一个select,输入0就是退出了,判断完后,就走到了选择任务


然后直接把cmdcode改为选择的select,-1是因为是从下标0开始的,输入1就是0下标的

测试

bug的地方:

这样会有一些bug(一个子进程不是只有一个写端(每一次子进程的创建都是有继承))

 这样会有一些bug(一个子进程不是只有一个写端(每一次子进程的创建都是有继承))

按理说这样是对的,可是这样就错了

因为下面两个红线还没有关掉,它们进程了最开始的w

这样倒着回收是可以的

正确改法

修改一下

最后一个push_back的就都是父进程的写入fd,

然后加一句这个红线的,每创建子进程后都先把上一次父进程的读端fd关掉就可以了,这里很妙,因为vector一开始是空的

方便看

这里这样就可以了        

管道已经完成

以上是匿名管道 

总文件总代码

makefile中代码

ProcessPool:ProcessPool.cc
	g++ -o $@ $^ -std=c++11
.PHNOY:clean
clean:
	rm -f ProcessPool

Task.hpp中代码

#pragma once

#include<iostream>
#include<vector>

using namespace std;

typedef void (*task_t)();

void task1()
{
    cout<< "lol 刷新日志" <<endl;
}

void task2()
{
    cout<< "lol 更新野区" <<endl;
}

void task3()
{
    cout<< "lol 检测软件更新" <<endl;
}

void task4()
{
    cout<< "lol 释放技能" <<endl;
}

ProcessPool.cc中代码

#include "Task.hpp"
#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<assert.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;

//打算创建5个进程
const int processnum = 5;
//全局任务
vector<task_t> tasks;


//先描述
class channel//管道
{ 
public:
    channel(int cmdfd,pid_t slaverid,string& processname)
    :_cmdfd(cmdfd)
    ,_slaverid(slaverid)
    ,_processname(processname)
    {}

public:
    int _cmdfd;//文件描述符
    pid_t _slaverid;//代表哪个子进程
    string _processname;//子进程的名字,方便打印日志
};

//子进程中读的任务
// void slaver(int rfd)
// {
//     while(true)
//     {
//         cout<< getpid() <<" - "<< "read fd is->"<<rfd<<endl;
//         sleep(1000);
//     }
// }
//改变一下从fd为0的地方开始读
void slaver()
{
    //read(0);
    while(true)
    {
        int cmdcode = 0;
        int n = read(0, &cmdcode, sizeof(int));
        if(n == sizeof(int))
        {
            //执行cmdcode对应的任务列表
            cout<< "slaver say@ get a command:" << getpid() << ":cmdcode:" << cmdcode <<endl;

            //判断一下并执行
            if(cmdcode >= 0 && cmdcode < tasks.size())  tasks[cmdcode]();
        }
        if(n == 0) break;
    }
}

//初始化
void Init(vector<channel>& channels)
{
    for(int i =0;i < processnum;i++)
    {
        int pipefd[2];
        int n = pipe(pipefd);//创建管道
        //返回值小于0就创建失败了
        assert(!n);
        (void)n;

        pid_t id = fork();
        if(id == 0)
        {
            //子进程:读
            close(pipefd[1]);

            //改变一下从fd为0的地方读
            dup2(pipefd[0],0);
            close(pipefd[0]);
            //任务
            slaver();
            cout<< "process: " << getpid() << "quit" <<endl;
            //slaver(pipefd[0]);

            exit(0);
        }
        //父进程:写
        close(pipefd[0]);
        //channel添加字段
        string name = "processs-" + to_string(i);
        //插入的是自定义类型,要构造一下,第一个传的是文件描述符,要写入的fd
        channels.push_back(channel(pipefd[1], id, name));
    }
}
//测试函数,纯输入函数
//输入:const &
//输出:*
//输入输出:&
void debug(const vector<channel>& channels)
{
    for(auto&e : channels)
    {
        cout<< e._cmdfd <<"  "<<e._slaverid<<"   "<<e._processname<<endl;
    }

}

void Loadtask(vector<task_t> *tasks)
{   
    tasks->push_back(task1);
    tasks->push_back(task2);
    tasks->push_back(task3);
    tasks->push_back(task4);
}
void memu()
{
    cout<< "########################" <<endl;
    cout<< "1:lol 刷新日志     2:lol 更新野区" <<endl;
    cout<< "1:lol 检测软件更新   4:lol 释放技能" <<endl;
    cout<< "           0:退出             " <<endl;
    cout<< "########################" <<endl;
}
//2:开始控制子进程
void ctrlSlaver(vector<channel> &channels)
{
    int which = 0;
    int cnt = 5;
    while(true)
    {
        int select = 0;
        memu();
        cout<< "Please Enter@:";
        cin>> select;
        if(select == 0) break;
        //1:选择任务
        //int cmdcode = rand()%tasks.size();
        int cmdcode = select - 1;

        //2:随机选择进程
        //int processpos = rand()%channels.size();
        
        //2:轮询选择进程
        cout<< "father say:"<< "cmdcode:" << cmdcode << "   already sendto  " <<channels[which]._slaverid << "process name   " 
            <<channels[which]._processname << endl;

        //3:发送任务
        write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode));
        which++;
        which%=channels.size();//保证不大于其长度
        cnt--;
        if(cnt == 0) break;
        sleep(1);
    }
}
void QuitProcess(const vector<channel> &channels)
{
    for(const auto& e : channels) close(e._cmdfd);
    sleep(10);
    for(const auto& e : channels) waitpid(e._slaverid, nullptr, 0);//进程的pid=_slaverid,关上了以后记得回收
}

int main()
{
    Loadtask(&tasks);

    //srand(time(nullptr)^getpid()^1023);//种一个随机数种子
    //在组织
    vector<channel> channels;
    //1:初始化
    Init(channels);

    debug(channels);

    //2:开始控制子进程
    ctrlSlaver(channels);

    //3:清理收尾
    QuitProcess(channels);

    return 0;
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2179410.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

新手上路:Anaconda虚拟环境创建和配置以使用PyTorch和DGL

文章目录 前言步骤 1: 安装 Anaconda步骤 2: 创建新的 Anaconda 环境步骤 3: 安装最新版本的 PyTorch步骤 4: 安装特定版本的 PyTorch步骤 5: 安装最新版本的 DGL步骤 6: 安装特定版本的 DGL步骤 7: Pycharm中使用虚拟环境解释器第一种情况&#xff1a;创建新项目第二种情况&am…

Linux Shell编程最重要的十个核心概念

对于每个Linux学习者而言&#xff0c;Shell编程的重要性不言而喻。它是连接用户与系统操作的桥梁&#xff0c;能够培养命令行的熟练度和解决问题的能力。通过Shell编程&#xff0c;学习者可以深入理解系统管理和日常任务自动化的精髓&#xff0c;提高工作效率。此外&#xff0c…

应急响应(1)-同事电脑

一、现象 重要时间节点&#xff0c;同事反馈桌面有鼠标移动、随机断网&#xff1b;发现登录账户多了一个&#xff0c;由于电脑长时间没有更改过密码&#xff0c;导致忘记密码无法登录。随机联系进行应急处理。 二、排查 2.1、密码重置/删除 由于同事忘记密码导致无法进行登录…

Linux文件系统入门详解-总结大章

我们先看一张图&#xff1a; 这张图大体上描述了 Linux 系统上&#xff0c;应用程序对磁盘上的文件进行读写时&#xff0c;从上到下经历了哪些事情。 这篇文章就以这张图为基础&#xff0c;介绍 Linux 在 I/O 上做了哪些事情。 文件系统 什么是文件系统 文件系统&#xff0…

EKF学习笔记

扩展卡尔曼滤波&#xff0c;也就是EKF&#xff0c;常用于在动态系统中对状态的估计。比如&#xff0c;在机器人领域&#xff0c;EKF则常常用于对状态&#xff08;位置&#xff0c;方向&#xff09;的估计&#xff0c;也就是我们常说的数据融合&#xff0c;结合运动模型和观测数…

vscode使用yarn 启动vue项目记录

第一次启动yarn项目&#xff0c;这个是公司的老项目&#xff0c;遇到了点问题&#xff0c;记录下首先是我一般使用的是npm命令&#xff0c;所以没有安装yarn vscode安装yarn vscode进入到该项目文件夹下&#xff0c;输入命令&#xff1a;npm install -g yarn 安装成功后&…

河南人社厅:注册满两年可按条件认定副高

河南省工程系列建筑专业中高级职称 申报评审标准 总则 一、为培养造就高素质、社会化的工程建设领域专业技术人才队伍&#xff0c;充分发挥工程建设领域专业技术人才在科技发展和经济建设中的作用&#xff0c;推动技术创新、科技成果转化和实现高新技术产业化&#xff0c;根据…

CSS给一行按钮统一设置间隔

使用css的&#xff0b;&#xff08;相邻兄弟选择器&#xff09;&#xff0c;找到指定元素后面的相邻元素。 <div class"btn-list"><button class"btn">按钮1</button><button class"btn">按钮2</button><butto…

Arthas memory(查看 JVM 内存信息)

文章目录 二、命令列表2.1 jvm相关命令2.1.11 memory&#xff08;查看 JVM 内存信息&#xff09;举例1&#xff1a;查看 JVM 内存信息 本人其他相关文章链接 二、命令列表 2.1 jvm相关命令 2.1.11 memory&#xff08;查看 JVM 内存信息&#xff09; 基本用法&#xff1a; mem…

第一讲-环境安装

PyCharm安装 官网下载 https://www.jetbrains.com/pycharm/ 点击Download跳转到下载界面,会有专业版跟社区版两个选择,专业版需要购买,可自行在淘宝上买个激活码。一般开发社区版够用了。 这是专业版: 这是社区版:

WinRAR技巧:如何给多个压缩包设置同一个密码

RAR压缩包是大家经常使用的文件&#xff0c;并且可以进行加密&#xff0c;也是一种文件加密方式&#xff0c;那么当你有很多文件都需要压缩加密&#xff0c;b并且想要设置同一个密码&#xff0c;防止以后忘记密码&#xff0c;该如何高效的完成这个工作呢&#xff1f;今天分享如…

vue2中使用tailwindCss 详细教程

1、先看官方文档&#xff1a;https://www.tailwindcss.cn/ 2、先安装&#xff1a;npm install -D tailwindcss ---------------通过 npm 安装 tailwindcss&#xff0c;然后创建你自己的 create your tailwind.config.js 配置文件。 npm install -D tailwindcss 3、初始化文件…

解决Java调用通义接口出现依赖爆红与API-key找不到(日常小记)

1.依赖dashscope-sdk-java爆红 解决方法&#xff1a; <dependency><groupId>com.alibaba</groupId><artifactId>dashscope-sdk-java</artifactId><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId…

跑批系统设计

需求分析 将大批量的数据&#xff0c;从一个地方&#xff0c;迁移到另外一个地方&#xff0c;如何处理 主要的涉及到的问题 亿级数据怎么存怎么防止重复调度怎么做到负载均衡同一个节点&#xff0c;任务怎么并行如何动态调整并发度机器节点挂了怎么办 概要设计 数据存储 …

springboot整合MybatisPlus+MySQL

上一篇&#xff1a;springboot整合sentinel和对feign熔断降级 文章目录 一、准备二、主要工作三、具体步骤3.1 准备数据库环境3.20 pre引入依赖3.2 引入依赖3.3 bootstrap.yml配置mybatisplus3.40 pre引入service、mapper3.4 引入实体类、service、mapper 四、测试目录结构 五…

数据结构 ——— 单链表oj题:移除链表中所有 val 的元素

目录 题目要求 手搓简易单链表 代码实现 题目要求 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回新的头节点 手搓简易单链表 在实现以上逻辑函数前&#xff0c;要先手搓一个单链表出来&#xff…

iOS--App启动过程及优化

前言 App启动是用户对于一个app的第一印象&#xff0c;因此如何使用户在最短的时间打开进入app显得格外重要。启动优化因此成为了App调优至关重要的一项。 只有具体了解了App的启动过程&#xff0c;我们才能对其进行优化。 App启动过程 App启动分为冷启动和热启动 热启动&…

公司申请商标注册需要什么材料

申请商标注册的&#xff0c;应当向商标局提交《商标注册申请书》及其它文件&#xff0c;具体要求是&#xff1a; 1、申请人必须按一类商品一件商标一份申请的原则&#xff0c;提交《商标注册申请书》一份。也即一份申请书上填报的商品或服务只能限定在《商标注册用商品和服务国…

Debian 配置 Python 开发与运行环境

配置 Python 开发与运行环境。 1.3.1. Debian下的安装与配置 Debian 是一个致力于自由软件开发并宣扬自由软件基金会理念的自愿者组织。 Debian 计划创建于 1993 年。当时&#xff0c;Ian Murdock 发出一份公开信&#xff0c; 邀请软件开发者们参与构建一个基于较新的 Linux …

Java8/9/10/11新特性

目录 一、 Lambda表达式二、函数式(Functional)接口三、方法引用与构造器引用3.1、方法引用3.2 构造器引用和数组引用3.2.1 构造器引用3.2.2 数组引用 四、 强大的Stream API4.1 Stream API说明4.2 Stream 的操作三个步骤4.3 创建 Stream方式4.4 、Stream 的中间操作4.4.1 筛选…