Linux-管道通信

news2024/11/24 17:30:36

1. 管道概念        

        管道,是进程间通信的一种方式,在Linux命令中“ | ”就是一种管道,它可以,连接前一条命令,和后一条命令,把前面命令处理完的内容交给后面,例如

cat filename | grep hello

        cat命令会把文件的内容打印在显示器上,而管道就把这些内容交给后面的 grep 命令,由 grep 处理过后再把内容打印到显示器上

        这就是管道,这两个命令可以看作,两个进程,管道负责把内容传输由一方传给另一方

        这也同时注定了,管道是一份共享的资源,被两个进程同时看到,并使用。

        再之前的学习中,我们有没有两个进程共享同一份资源的呢?

        有,fork创建子进程,父子进程同时打印数据到显示器上,这是不是也是一种资源的共享,这是利用文件系统,当父进程创建子进程,子进程会拷贝父进程的大部分内容,包括PCB(task_struct),代码和数据,以及文件描述符表files_struct,子进程会复制父进程对应的file*指针,这也就导致他们指向同一个文件,所以他们可以同时向屏幕写数据。

b1b40997dc984a22b489c503bbb4e7fa.png

2. 匿名管道

        基于上面的启发,对于父子进程之间,我们是不是也可以通过打开同一份文件,一方读,另一方写,进而达成通信的目的。

具体步骤:

        1. 父进程先以读写的方式打开同一份文件

        2. fork创建子进程

        3. 父进程关闭读端,子进程关闭写端     

2b911205d2d64a9189fe42e8cfa1761c.png

        这样就形成了一个管道,父进程负责向管道写数据,子进程负责向管道读数据

        但是单纯是文件,还不行,因为文件会有IO操作,如果内存文件和磁盘进行读写,会大大降低效率,而管道通信,只需要一个临时的内存级文件,不需要向磁盘写入和读取,所以这就需要系统提供的pipe函数

574ebfcd7f2641b2922546490f65dda7.png

3fe115aa9af54eed8619992b07750861.png

        pipe会创建一个管道,用于进程间通信,他会打开并创建一个内存级的临时文件,其中参数是一个输出型参数,需要自己定义一个数组,函数调用后,pipefd[0]是进程的读方式打开文件返回的fd,pipefd[1]是写端。返回值,0标识成功,-1标识失败。

        这就是匿名管道。

        我们可以发现匿名管道有以下几个特点

  • 匿名管道建立的基础是具有血缘关系的进程,来完成进程间通信,父子进程
  • 匿名管道,所以他的生命周期随着进程,当没有进程再打开他,他就会被删除
  • 管道的本质是文件,管道是基于文件的读写,read和write都是面向流的,面向字节流
  • 管道是半双工的,数据只能向一个方向流动,需要双方通信时,需要建立起两个管道
  • 管道还具有访问控制,通过让进程间协同,访问控制主要体现在:
  • a. 当写快,读慢,管道被写满了就不会再写,也就是写的进程等待,本质是阻塞
  • b. 当写慢,读快,管道没有数据时,就不会再读,也是阻塞式的等待
  • c. 当写关闭,读0,读到0,read的返回值为0代表读到文件结尾
  • d. 当读关闭,写继续写,OS就会终止写方的进程SIGPIPE

匿名管道间通信的实验:

process.cc

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "task.hpp"

using namespace std;

#define Child_Process_Size 5

int command_wait(int fd, bool &flag)
{
    uint32_t command = 0;
    ssize_t sz = read(fd, &command, sizeof(command));
    // cout << "command:" << command << endl;
    // cout << "sz:" << sz << endl;
    if (sz == 0)
        flag = true;
    else
        assert(sz == sizeof(command));
    return command;
}

void command_push(pid_t child_process, int fd, int command)
{
    cout << "main:child_process" << child_process << "\tfd" << fd << "\tcommand" << command << endl;
    ssize_t ret = write(fd, &command, sizeof(command));
    // cout << ret << "!" << endl;
}

int main()
{
    load();
    vector<pair<pid_t, int>> slots;
    for (int i = 0; i < Child_Process_Size; ++i)
    {
        // 建立管道
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        assert(n == 0);
        (void)n;
        pid_t id = fork();
        assert(id != -1);

        if (id == 0)
        {
            // child
            // 关闭信道
            close(pipefd[1]);
            while (true)
            {
                bool flag = false;
                int command = command_wait(pipefd[0], flag);
                // cout << command << "#" << endl;
                // printf("%p888888\n", callbacks[command]);
                if (flag)
                    break;
                if (command >= 0 && command < callbacks.size())
                {
                    callbacks[command]();
                }
                else
                {
                    cout << "非法command:" << command << endl;
                }
            }
            exit(1);
            // 执行command
        }
        // father
        close(pipefd[0]);
        slots.push_back(pair<pid_t, int>(id, pipefd[1]));
    }

    // 派发任务
    srand((unsigned int)time(nullptr));
    int time = 5;
    随机派发
    // while(time--)
    // {
    //     int command = rand() % callbacks.size();

    //     int child = rand() % Child_Process_Size;

    //     command_push(slots[child].first, slots[child].second, command);

    //     sleep(1);
    // }
    指定派发
    while (true)
    {
        cout << "******************************" << endl;
        cout << "** 1. describe   2. command **" << endl;
        cout << "*********  0. quit  **********" << endl;
        cout << "please cin your choice:";
        int child = rand() % Child_Process_Size;
        int command;
        bool quit = false;
        int choice = -1;
        cin >> choice;
        switch (choice)
        {
        case 1:
            describe();
            break;
        case 2:
            cout << "please cin command:";
            cin >> command;
            command_push(slots[child].first, slots[child].second, command);
            break;
        case 0:
            quit = true;
            break;
        default:
            cout << "please recin:";
            break;
        }
        if (quit)
            break;
    }

    for (const auto &e : slots)
    {
        close(e.second);
    }

    for (const auto &e : slots)
    {
        pid_t ret = waitpid(e.first, nullptr, 0);
        assert(ret >= 0);
    }

    return 0;
}

 task.hpp

#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <functional>
#include <unistd.h>

using namespace std;

typedef std::function<void()> func;

unordered_map<int, string> desc;
vector<func> callbacks;

void mystrcpy()
{
    cout <<"child process" << getpid() << "字符串拷贝" << endl;
}

void mystrstr()
{
    cout <<"child process" << getpid() <<  "字符串匹配" << endl;
}

void mystrcmp()
{
    cout <<"child process" << getpid() <<  "字符串比较" << endl;
}

void mystrlen()
{
    cout <<"child process" << getpid() <<  "字符串计数" << endl;
}

void load()
{
    desc.insert({callbacks.size(), "mystrcpy:字符串拷贝"}); 
    callbacks.push_back(mystrcpy);

    desc.insert({callbacks.size(), "mystrstr:字符串匹配"});    
    callbacks.push_back(mystrstr);

    desc.insert({callbacks.size(), "mystrcmp:字符串比较"});      
    callbacks.push_back(mystrcmp);

    desc.insert({callbacks.size(), "mystrlen:字符串计数"});
    callbacks.push_back(mystrlen);
}

void describe()
{
    for(const auto& e : desc)
    {
        cout << e.first << "\t" << e.second << endl;
    }
}

3. 命名管道

        命名管道(IFO)是基于匿名管道(pipe)的的更进一步,匿名管道只能在有血缘关系的进程间通信,而命名管道没有这个限制。

        创建命名管道有两种方式:

        a. 命令行

mkfifo name_fifo

e38b5c6271fd4579bc548d3909a804c4.png         命名管道文件属性为p,文件没有内容,也是内存级文件,他只有inode属性,没有data block数据块,数据都在内存

        b. 函数

04fb1877fcf54a8dad567283aa0f224f.png

        参数pathname文件路径,你需要把命名管道文件创建在那个路径下,mode文件属性,mode&~umask为最终文件属性,例如0666,最终文件属性664(rw_rw_r__),返回值0成功,-1失败

        命名管道和匿名管道在使用上的区别,匿名管道由pipe函数创建并打开命名管道由mkfifo函数创建,打开用open

        匿名管道的删除不需要额外动作,进程结束,管道就没了。但命名管道不同,命名管道需要删除,他是存在磁盘上的,只有属性没有内容。两种方式:

        a. 命令行

rm name_fifo
unlink name_fifo

        b. 函数

6b4fc338d0af40a9bf54239da8ab866a.png

        匿名管道和命名管道,本质都是文件,除了上面的一些区别,其他的使用都是一样的,而且也是具有访问控制,都是文件嘛!

关于命名管道的实验:

head.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <cstdlib>
#include <ctime>
#include "log.hpp"

using namespace std;

#define SIZE 1024
#define MODE 0666

string ipcPath = "./fifo";

log.hpp

#pragma once

#include <ctime>
#include <iostream>
#include <string>

enum status
{
    Debug,
    Notice,
    Warning,
    Error
};

const std::string str[] = {
    "Debug",
    "Notice",
    "Warning",
    "Error"
};

std::ostream& log(std::string message, status level)
{
    std::cout << str[level] << (unsigned int)time(nullptr) << ":" << message;
    return std::cout;
}

client.cc

#include "head.hpp"

int main()
{
    int fd = open(ipcPath.c_str(), O_WRONLY);
    if(fd < 0)
    {
        perror("open");
        exit(1);
    }
    string message;
    while(true)
    {
        cout << "client[" << getpid() << "]:";
        cin >> message;
        write(fd, message.c_str(), message.size());
        sleep(1);
    }
    close(fd);
    return 0;
}

server.cc

#include "head.hpp"


void read_fifo(int fd)
{
    char buffer[SIZE];
    while(true)
    {
        memset(buffer, '\0', SIZE);
        ssize_t sz = read(fd, buffer, SIZE - 1);
        if(sz > 0)
        {
            buffer[sz] = '\0';
            cout << "server[" << getpid() << "]:" << buffer << endl;
        }
        else if(sz == 0)
        {
            break;
        }

    }
}

int main()
{
    int ret = mkfifo(ipcPath.c_str(), MODE);
    if(ret == -1)
    {
        perror("mkfifo");
        exit(1);
    }
    log("创建命名管道成功", Debug) << "step 1" << endl;
    int fd = open(ipcPath.c_str(), O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        exit(2);
    }
    log("以读方式打开管道成功", Debug) << "step 2" << endl;
    for(int i = 0; i < 3; ++i)
    {
        int _fd = fork();
        if(_fd < 0)
        {
            perror("fork");
            exit(3);
        }
        if(_fd == 0)
        {
            //child
            read_fifo(fd);
            exit(1);
        }
        
    }
    for(int i = 0; i < 3; ++i)
    {
        pid_t ret = waitpid(-1, nullptr, 0);
        if(ret > 0)
        {
            string str = "等到子进程:";
            string id = to_string(ret);
            log(str+id, Notice);
        }
    }   
    close(fd);
    log("关闭打开的管道", Debug) << "step 3" << endl;
    unlink(ipcPath.c_str());
    log("删除管道文件成功", Debug) << "step 4" << endl;
    return 0;
}

完。

 

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

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

相关文章

富文本编辑器CKEditor4简单使用-07(处理浏览器不支持通过工具栏粘贴问题 和 首行缩进的问题)

富文本编辑器CKEditor4简单使用-07&#xff08;处理浏览器不支持通过工具栏粘贴问题 和 首行缩进的问题&#xff09; 1. 前言——CKEditor4快速入门2. 默认情况下的粘贴2.1 先看控制粘贴的3个按钮2.1.1 工具栏粘贴按钮2.1.2 存在的问题 2.2 不解决按钮问题的情况下2.2.1 使用ct…

三维图形学知识分享---求平面与模型相交线

在CGAL&#xff08;Computational Geometry Algorithms Library&#xff09;中&#xff0c;Polygon_mesh_processing模块提供了用于处理多边形网格数据结构的功能。其中&#xff0c;surface_intersection函数是用来计算模型的表面相交线的工具。 CGAL_Mesh mesh_orcl;std::vect…

C++ 函数 参数与返回值

#一 参数与返回值 回顾文件读数据功能 文件读数据 1函数参数传值调用过程 将函数调用语句中的实参的一份副本传给函数的型材。 简单的值的传递&#xff0c;实参的值没有发生变化。 2 函数参数传值调用过程 传地址调用 将变量的地址传递给函数的形参 形参和实参指向了同…

SpringBoot文件上传+拦截器

1、resource static下有个图片&#xff0c;希望浏览器可以查看这个图片 访问&#xff1a; 若yml设置路径&#xff0c;则可以定义在static下才可以访问 classpath代表类路径&#xff0c;都在target下 也就是项目在运行后的resource下的文件都会到classes下去 无需在target下创…

MES(制造执行系统)与PDCA循环,斩不断理还乱的关系。

MES系统算是B端系统中比较复杂的一种&#xff0c;这与我国制造业标准化程度较低有一定的关联&#xff0c;MES的存在就是要更好执行PDCA循环&#xff0c;二者关联是千丝万缕的&#xff0c;B系统提升专家借此为大家分享一下。 一、什么是PDCA PDCA&#xff08;Plan-Do-Check-Ac…

前端Web开发基础知识

HTML定义 超文本标记语言&#xff08;英语&#xff1a;HyperText Markup Language&#xff0c;简称&#xff1a;HTML&#xff09;是一种用于创建网页的标准标记语言。 什么是 HTML? HTML 是用来描述网页的一种语言。 HTML 指的是超文本标记语言: HyperText Markup LanguageH…

# IDEA 复制项目 Module 出现 不同模块下的 Product 类报错

IDEA 复制项目 Module 出现 不同模块下的 Product 类报错 我们 用 IDEA 复制项目 Module 出现 不同模块下的 Product 类报错&#xff0c;发现复制的 module 名称没有改变或者 java 文件夹后面还有原项目 source root 字样&#xff0c;maven 父子项目没有标识等问题。 解决方法…

QQ+微信聊天记录分析工具,allin~

QQ群 ... QQ个人 微信群 个人朋友圈 更多维度有待探索~ 工具下载 TencentRecordAnalysisV1.0.2.zip 蓝奏云&#xff1a;链接: lanzoub.com/b00rn0g47e 密码:9hww 百度云&#xff1a;链接: pan.baidu.com/s/1Gf5EpJ 提取码: hp2p

Stm32CubeMX 为 stm32mp135d 添加 adc

Stm32CubeMX 为 stm32mp135d 添加 adc 一、启用设备1. adc 设备添加2. adc 引脚配置2. adc 时钟配置 二、 生成代码1. optee 配置 adc 时钟和安全验证2. linux adc 设备 dts 配置 bringup 可参考&#xff1a; Stm32CubeMX 生成设备树 一、启用设备 1. adc 设备添加 启用adc设…

R语言学习—1—将数据框中某一列数据改成行名

将数据框中某一列数据改成行名 代码 结果

DHCPv4_CLIENT_ALLOCATING_03: 发送DHCPREQUEST - 必须包含‘服务器标识符‘

测试目的&#xff1a; 验证客户端发送的DHCPREQUEST消息中是否包含“服务器标识符”选项&#xff0c;以指示它选择的服务器。 描述&#xff1a; 本测试用例旨在确保DHCP客户端在广播DHCPREQUEST消息时&#xff0c;必须包含“服务器标识符”选项。该选项用于指明客户端选择了…

Universal Thresholdizer:将多种密码学原语门限化

参考文献&#xff1a; [LS90] Lapidot D, Shamir A. Publicly verifiable non-interactive zero-knowledge proofs[C]//Advances in Cryptology-CRYPTO’90: Proceedings 10. Springer Berlin Heidelberg, 1991: 353-365.[Shoup00] Shoup V. Practical threshold signatures[C…

[嵌入式系统-53]:嵌入式系统集成开发环境大全 ( IAR Embedded Workbench(通用)、MDK(ARM)比较 )

目录 一、嵌入式系统集成开发环境分类 二、由MCU芯片厂家提供的集成开发工具 三、由嵌入式操作提供的集成开发工具 四、由第三方工具厂家提供的集成开发工具 五、开发工具的整合 5.1 Keil MDK for ARM 5.2 IAR Embedded Workbench&#xff08;通用&#xff09;、MDK&…

240503-关于VisualStudio2022社区版的二三事

240503-关于VisualStudio2022社区版的二三事 1 常用快捷键 快捷键描述AltEnter选中代码片段以提取方法Alt上下箭头移动选中的代码片段F12转到方法定义CtrlR*2批量修改选中的变量名称 2 自动生成构造函数 3 快速重写父类方法 4 节约时间&#xff1a;写代码使用“头插法”&…

深度解析 Spring 源码:从BeanDefinition源码探索Bean的本质

文章目录 一、BeanDefinition 的概述1.1 BeanDefinition 的定位1.2 BeanDefition 的作用 二、BeanDefinition 源码解读2.1 BeanDefinition 接口的主要方法2.2 BeanDefinition 的实现类2.2.1 实现类的区别2.2.2 setBeanClassName()2.2.3 getDependsOn()2.2.4 setScope() 2.3 Bea…

用双目相机实现坐标标定

一&#xff1a;相机参数设置和计算 镜头参数&#xff1a;MF2808-10MP 靶面尺寸2/3 &#xff0c;视场角&#xff08;对角水平垂直&#xff09; 69.758.545.5 焦距&#xff1a;8mm&#xff0c;分辨率&#xff1a;16241240 1.1视场角的计算 图像分辨率越高&#xff0c;双目匹…

FP16、BF16、INT8、INT4精度模型加载所需显存以及硬件适配的分析

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

Arduino 推出带 Wi-Fi的 32 位 UNO 板

Arduino 推出了下一代 UNO 板&#xff0c;引入了 32 位 Renesas 微控制器和 Espressif ESP32-S3 模块、一键云连接和大量 I/O 以及 128 红色 LED 矩阵。新型 UNO R4 板有两个版本&#xff0c;带 Wi-Fi 连接和不带 Wi-Fi 连接&#xff0c;并保持了 UNO R3 的外形尺寸、屏蔽兼容性…

分布式事务—> seata

分布式事务之Seata 一、什么是分布式事务&#xff1f; 分布式事务是一种特殊类型的事务&#xff0c;它涉及多个分布式系统中的节点&#xff0c;包括事务的参与者、支持事务的服务器、资源服务器以及事务管理器。 在分布式事务中&#xff0c;一次大型操作通常由多个小操作组成…

jvm垃圾回收机制介绍

JVM&#xff08;Java虚拟机&#xff09;是Java程序的运行环境&#xff0c;它负责执行字节码文件。JVM的工作原理主要包括以下几个部分&#xff1a;类加载器、执行引擎、垃圾收集器和内存管理。类加载器负责加载字节码文件并将其转换成Java平台上的机器码&#xff0c;执行引擎负…