Linux-管道通信

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.mfbz.cn/a/582036.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

本地部署 Meta Llama3-8b

Meta Llama 3(8B) Instruct model performance Meta Llama 3(8B) Pre-trained model performance 使用 Ollama 运行 Llama3 访问 Tags llama3&#xff0c;选择你想运行的模型&#xff0c;例如&#xff0c;8b-instruct-q8_0 拷贝并运行命令&#xff0c;ollama run llama3:8b-…

秋招后端开发面试题 - Java语言基础(上)

目录 Java基础上前言面试题Java 语言的特点JVM JDK JRE什么是跨平台性&#xff1f;原理是什么&#xff1f;什么是字节码?采用字节码的好处是什么?Java 和 C 的区别&#xff1f;注释&#xff1f;关键字关键字 instanceof类型转换关键字 this 和 super关键字 final finally fin…

Android数据恢复:如何在手机上恢复丢失的文件和照片

我们都有 我们错误地从手机中删除重要内容的时刻。确实如此 不一定是我们的错。其他人可以对您的手机数据执行此操作 有意或无意。这在某个时间点发生在我们所有人身上。 但是&#xff0c;今天市场上有各种各样的软件可以 帮助恢复已删除的文件。这些类型的软件被归类为数据恢复…

Linux命令大全 以及搭建hadoop

Liunx系统目录 ├── bin -> usr/bin # 用于存放二进制命令 ├── boot # 内核及引导系统程序所在的目录 ├── dev # 所有设备文件的目录&#xff08;如磁盘、光驱等&#xff09; ├── etc # 配置文件默认路径、服务启动命令存放目录 ├── home # 用户家目录&#…

UEFI安全启动模式下安装Ubuntu的NVIDIA显卡驱动

UEFI安全启动模式下安装ubuntu的nvidia显卡驱动 实践设备&#xff1a;华硕FX-PRO&#xff08;NVIDIA GeForce GTX 960M&#xff09; 一、NVIDIA官网下载驱动 1.1在浏览器地址栏输入https://www.nvidia.cn/drivers/lookup/进入网站&#xff0c;接着手动驱动搜索&#xff0c;并…

【C语言】:动态内存管理

1、为什么要有动态内存分配2、动态内存管理函数2.1 malloc2.2 free2.3 calloc2.4 realloc 3、常见的动态内存的错误3.1 对NULL指针的解引用操作3.2 对动态开辟空间的越界访问3.3 对非动态开辟内存使用free释放3.4 使用free释放一块动态开辟内存的一部分3.5 对同一块动态内存多次…

stm32单片机开发三、DMA

DMA其实就是一种将ADC的数据寄存器、串口的数据寄存器等等一些数据放到sram中特定位置&#xff0c;方便CPU去读取 比如ADC转换&#xff0c;DMA直接转换的ADC的值放在内存中的特定位置&#xff0c;CPU可以直接去读取 uint16_t AD_Value[4]; //定义用于存放AD转换结果的全局…

告别繁琐!小浪助手助你轻松下载学浪视频

小浪助手&#xff0c;一站式学浪视频下载工具&#xff0c;能让你轻松下载学浪视频 工具我已经打包好了&#xff0c;有需要的自己取一下 学浪下载器链接&#xff1a;https://pan.baidu.com/s/1djUmmnsfLEt_oD2V7loO-g?pwd1234 提取码&#xff1a;1234 --来自百度网盘超级会…

Pycharm配深度学习环境所遇到的部分问题

问题1&#xff1a;Anaconda prompt界面安装CUDA出现的问题: 不管是&#xff1a;conda install pytorch torchvision torchaudio cudatoolkit11.3 -c pytorch 还是:pip ****什么的 问题描述&#xff1a;EnvironmentNotWritableError: The current user does not have write p…

python应用-socket网络编程(1)

目录 1 先简单回顾下客户端和服务端通信的知识 2 服务端常用函数 3 客户端常用函数 4 服务端和客户端都用的函数 5 示例介绍客户端和服务端通信过程 6 建立服务端套接制 7 创建服务端函数socket.create_server() 8 创建客户端套接字 9 客户端连接函数socket.create_co…

用socat验证multicase(组播)和broadcast(广播) with k8s容器环境

安装socat 网络允许的话&#xff0c;可以使用yum install -y socat进行安装。 如果是在容器里面运行&#xff0c;建议使用静态链接的socat: 可以从此处下载&#xff1a; Release socat-v1.7.4.4 ernw/static-toolbox GitHub 确定网络接口地址和组播地址 #kubectl exec -i…

请编写函数fun,该函数的功能是:实现B=A+A‘,即把矩阵A加上A的转置,存放在矩阵B中。计算结果在main函数中输出。

本文收录于专栏:算法之翼 https://blog.csdn.net/weixin_52908342/category_10943144.html 订阅后本专栏全部文章可见。 本文含有题目的题干、解题思路、解题思路、解题代码、代码解析。本文分别包含C语言、C++、Java、Python四种语言的解法完整代码和详细的解析。 题干 请编…

ASP.NET汽车销售管理系统的设计与开发

摘 要 随着人们生活水平的不断提高&#xff0c;人们对汽车的消费和需求也越来越旺盛。很多汽车销售公司的业务环节仍然运用人工记账的传统方法&#xff0c;既容易出错又会导致账目混乱&#xff0c;查询和统计起来也非常不方便&#xff0c;费时又费力&#xff0c;严重时会给公…

Python | Leetcode Python题解之第58题最后一个单词的长度

题目&#xff1a; 题解&#xff1a; class Solution:def lengthOfLastWord(self, s: str) -> int:ls[]for i in s.split():ls.append(i)return len(ls[-1])

CLI举例:基于用户接入方式和终端设备类型的安全策略

组网需求 如图1所示&#xff0c;某企业在网络边界处部署了FW作为安全网关&#xff0c;连接内部网络与Internet。具体情况如下&#xff1a; 内部网络中已经部署了Agile Controller身份验证机制&#xff0c;Agile Controller服务器上存储了用户信息。内部网络中的用户可以使用不…

畅信达呼叫中心通过麒麟软件适配认证,引领行业新标准

在数字化浪潮汹涌的时代&#xff0c;软件兼容性已成为衡量一个企业技术实力和市场竞争力的重要标准。近日&#xff0c;畅信达呼叫中心产品成功通过麒麟软件适配认证&#xff0c;标志着产品在通用兼容性、性能及可靠性方面均达到了行业领先水平&#xff0c;将为用户的关键性应用…

Spring Boot整合Camunda实现工作流

文章目录 1. 环境准备2. 添加Camunda配置3. 创建并部署工作流程4. 启动Spring Boot应用程序5. 部署工作流程6. 访问Camunda Tasklist7. 执行工作流程8. 总结 &#x1f389;欢迎来到SpringBoot框架学习专栏~ ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1f379;✨博客主页&#xff1a…

deepflow grafana plugin 编译问题解决

修改tsconfig.js 增加"noImplicitAny": false&#xff0c;解决代码类型没有指定&#xff0c;显示Any 错误 To solve the error, explicitly set the parameters type to any, use a more specific type or set noImplicitAny to false in tsconfig.json. https://b…

vue处理查询框清除后无法查询问题,举例为日期选择

例如 在对应的查询方法添加 //我这里获取的是date&#xff0c;如果是其他参数改为其他的即可 if (query.date && query.date.length > 2) {conditions.noedate query.date[0] || conditions.noedate;//获取开始时间conditions.twodate query.date[1] || conditi…

CVE-2023-48795修复方法

CVE-2023-48795修复方法 1、修复原理2、检测漏洞3、修复漏洞4、成功修复 近期nessus扫描经常出现一个中危漏洞CVE-2023-48795&#xff0c;但网上的修复方法清一色的将openSSH升级到9.6p1版本 而我在升级当中处处碰壁&#xff0c;甚至于差点给服务器都干崩溃&#xff0c;特意研究…
最新文章