linux——进程间通信——管道

news2024/11/30 15:39:19

 ✅<1>主页::我的代码爱吃辣
📃<2>知识讲解:Linux——进程间通信——管道通信
☂️<3>开发环境:Centos7
💬<4>前言:进程间通信(InterProcess Communication,IPC)是指在不同进程之间传播或交换信息。

目录

一.什么是进程间通信

二.进程间通信目的

 三.进程间通信发展

四.什么是管道

五.匿名管道

 六.父子进程管道通信

1.匿名管道的场景与特点

2. 用fork来共享管道原理

 七.基于匿名管道实现进程池


一.什么是进程间通信

进程间通信(InterProcess Communication,IPC)是指在不同进程之间传播或交换信息。

二.进程间通信目的

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

 三.进程间通信发展

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

四.什么是管道

  • 管道是Unix中最古老的进程间通信的形式。
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。

五.匿名管道

头文件:#include <unistd.h>
功能:创建一无名管道。
原型:int pipe(int fd[2]);
参数:fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端。
返回值:成功返回0,失败返回错误代码。

 测试代码:

pipe.cc:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
    int fds[2]; // f[0]管道读端,f[1]管道写端
    char buf[100];
    int len;
    // 创建管道
    if (pipe(fds) == -1)
        perror("make pipe"), exit(1);

    // read from stdin
    while (fgets(buf, 100, stdin))
    {
        len = strlen(buf);
        // 写入管道
        if (write(fds[1], buf, len) != len)
        {
            perror("write to pipe");
            break;
        }

        memset(buf, 0, sizeof(buf));

        // 从管道中读取
        if ((len = read(fds[0], buf, 100)) == -1)
        {
            perror("read from pipe");
            break;
        }

        // 写入显示器
        if (write(1, buf, len) != len)
        {
            perror("write to stdout");
            break;
        }
    }
}

 makefile:

pipe:pipe.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -rf  pipe

测试结果:

 六.父子进程管道通信

我们知道fork之后,子进程会继承父进程的代码,数据会发生写时拷贝,那么父进程的文件描述符会不会被继承呢?会的。那么父进程创建的管道,其中的两个文件描述符一个指向管道的读端,一个指向管道的写端,也会被子进程继承。

1.匿名管道的场景与特点

管道的特点:

  1. 管道只具有单向通信的功能。
  2. 管道的本质是文件,因为fd的生命周期是随进程的,所以管道的生命周期也是随进程的。
  3. 管道通信,通常用来进行具有“血缘”关系的进程,进行进程间的通信。常用父子进程通信,
  4. 在管道的通信中,写入的次数,和读取的次数,不是严格匹配的,读写次数没有强相关,是面向字节流。
  5. 具有一定的协同能力,如果写端没有写入,读端会被阻塞——自带同步机制。

特殊场景:

  1. 如果我们read读取完毕了所有的管道数据,如果对方不发,我就只能等待。
  2. 如果我们writer端将管道写满了,我们还能写吗?不能
  3. 如果我关闭了写端,读取完毕管道数据,在读,就会read返回0,表明读到了文件结尾
  4. 写端一直写,读端关闭,会发生什么呢?没有意义。OS不会维护无意义,低效率,或者浪费资源的事情。OS会杀死一直在写入的进程!OS会通过信号来终止进程,(13)SIGPIPE。

测试代码:

#include <iostream>
#include <string>
#include <cerrno>
#include <cassert>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    // 让不同的进程看到同一份资源!!!!
    // 任何一种任何一种进程间通信中,一定要 先 保证不同的进程之间看到同一份资源
    int pipefd[2] = {0}; // pipefd[0] 读端, pipe[1]写端

    // 1. 创建管道
    int n = pipe(pipefd);
    if (n < 0)
    {
        std::cout << "pipe error, " << errno << ": " << strerror(errno) << std::endl;
        return 1;
    }
    std::cout << "pipefd[0]: " << pipefd[0] << std::endl; // 读端
    std::cout << "pipefd[1]: " << pipefd[1] << std::endl; // 写端

    // 2. 创建子进程
    pid_t id = fork();
    assert(id != -1);

    if (id == 0) // 子进程
    {
        // 3. 关闭不需要的fd,让父进程进行读取,让子进程进行写入
        close(pipefd[0]);

        // 4. 开始通信 -- 结合某种场景
        const std::string namestr = "hello,我是子进程";
        int cnt = 1;
        char buffer[1024];
        while (true)
        {
            snprintf(buffer, sizeof buffer, "%s, 计数器: %d, 我的PID: %d", namestr.c_str(), cnt++, getpid());
            write(pipefd[1], buffer, strlen(buffer));
            sleep(1);
        }
        // 退出时关闭打开的文件描述符
        close(pipefd[1]);
        exit(0);
    }

    // 父进程
    // 3. 关闭不需要的fd,让父进程进行读取,让子进程进行写入
    close(pipefd[1]);

    // 4. 开始通信 -- 结合某种场景
    char buffer[1024];
    int cnt = 0;
    while (true)
    {
        // sleep(10);
        // sleep(1);
        int n = read(pipefd[0], buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            buffer[n] = '\0';
            std::cout << "我是父进程, child give me message: " << buffer << std::endl;
        }
        else if (n == 0)
        {
            std::cout << "我是父进程, 读到了文件结尾" << std::endl;
            break;
        }
        else
        {
            std::cout << "我是父进程, 读异常了" << std::endl;
            break;
        }
        sleep(1);
        if (cnt++ > 5)
            break;
    }
    // 父进程读端关闭,子进程会收到13号信号
    close(pipefd[0]);

    // 回收子进程的僵尸状态
    int status = 0;
    waitpid(id, &status, 0);
    std::cout << "子进程pid:" << id << ",收到的信号sig: " << (status & 0x7F) << std::endl;

    sleep(1);

    return 0;
}

测试结果:

2. 用fork来共享管道原理

 

 

所以,看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想”。

 七.基于匿名管道实现进程池

当没有数据可读时,read调用阻塞,即进程暂停执行,一直等到有数据来到为止。

如果我们使用,父进程来控制写端,子进程进行读取,发送数据让子进程执行特定的任务,我们就可以实现对子进程的控制。

代码:

CtrlProc.cc:

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

using namespace std;
#define NUM_PROC 5

struct child_pip
{
    child_pip(int fd, pid_t pid)
        : _fd(fd), _pid(pid)
    {
    }
    ~child_pip()
    {
    }

    int _fd;
    pid_t _pid;
};

void WaitCommand()
{
    Task task;
    int command;
    while (1)
    {
        size_t n = read(0, &command, sizeof(int));
        if (n == 4) // 读取成功
        {
            task.funcs[command]();
        }
        else if (n == 0) // 读取失败
        {
            break;
        }
        else
        {
            break;
        }
    }
}
void creatproc(vector<child_pip> &child_pip_v)
{
    for (int i = 0; i < NUM_PROC; i++)
    {
        // 1.创建管道
        int pipfd[2];
        pipe(pipfd);

        // 2.创建子进程
        pid_t pid = fork();
        if (pid < 0)
            perror("fork");

        // 我们想让子进程从管道读,父进程向管道写
        if (pid == 0) // 子进程
        {
            // 3.关闭不必要的文件描述符
            close(pipfd[1]);
            // 3.1重定向,将来子进程指向0号文件描述符读取
            dup2(pipfd[0], 0);

            WaitCommand();

            exit(0);
        }
        // 父进程
        //  3.关闭不必要的文件描述符
        cout << "子进程pid:" << pid << endl;
        close(pipfd[0]);

        // 建立好子进程与管道的映射,父进程的写端口,和子进程pid
        child_pip_v.push_back(child_pip(pipfd[1], pid));
    }
}

void ctrlproc(vector<child_pip> &child_pip_v)
{
    while (1)
    {
        int command = 0;
        cin >> command;
        if (command == -1)
            break;
        int i = rand() % NUM_PROC;
        write(child_pip_v[i]._fd, &command, sizeof(int));
    }
}

void waitproc(vector<child_pip> &child_pip_v)
{
    int status = 0;
    for (int i = 0; i < child_pip_v.size(); i++)
    {
        close(child_pip_v[i]._fd);
    }
    // sleep(5);
    for (int i = 0; i < child_pip_v.size(); i++)
    {
        waitpid(child_pip_v[i]._pid, &status, 0);
        cout << "子进程:" << child_pip_v[i]._pid << "退出" << endl;
    }
}

int main()
{

    vector<child_pip> child_pip_v;
    creatproc(child_pip_v);

    ctrlproc(child_pip_v);

    waitproc(child_pip_v);

    return 0;
}

 Task.cc:

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

using namespace std;
typedef void (*fun_t)();

void beatxyy()
{
    cout << "子进程:" << getpid() << ",执行数据库" << endl;
}

void beatxyf()
{
    cout << "子进程:" << getpid() << ",写入日志" << endl;
}

void beatwy()
{
    cout << "子进程:" << getpid() << ",读取网卡" << endl;
}

void beatwj()
{
    cout << "子进程:" << getpid() << ",刷新缓冲区" << endl;
}

void beatxjy()
{
    cout << "子进程:" << getpid() << ",数据比对" << endl;
}

struct Task
{
    Task()
    {
        funcs.push_back(beatxyy);
        funcs.push_back(beatxyf);
        funcs.push_back(beatwy);
        funcs.push_back(beatwj);
        funcs.push_back(beatxjy);
    }

    vector<fun_t> funcs;
};

makefile:

CtrlProc:CtrlProc.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -rf CtrlProc

测试结果:

 注意:

为什么这里的waitproc我们要分开成两个循环,如果一个循环,文件描述符会无法关闭完,子进程也就无法退出。

我们关闭了第一个文件描述符,第一个管道由于继承问题,第一个管道还会有后面的子进程也会指向。最终导致我们只能有最后一个子进程先退出了,其他子进程进程陆续退出,此时进程等待也已经结束了,除了最后一个子进程。其他子进程的僵尸状态都没有被回收。

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

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

相关文章

Linux 文件上传、下载

1、通过FinalShell工具虚拟机进行数据交换 在FinalShell软件的下方窗体中&#xff0c;提供了Linux的文件系统视图&#xff0c;可以方便的&#xff1a; 浏览文件系统&#xff0c;找到合适的文件&#xff0c;右键点击下载&#xff0c;即可传输到本地电脑 浏览文件系统&#xff0…

密码技术 (5) - 数字签名

一. 前言 前面在介绍消息认证码时&#xff0c;我们知道消息认证码虽然可以确认消息的完整性&#xff0c;但是无法防止否认问题。而数字签名可以解决否认的问题&#xff0c;接下来介绍数字签名的原理。 二. 数字签名的原理 数字签名和公钥密码一样&#xff0c;也有公钥和私钥&am…

网站使用SSL证书是趋势吗?

随着互联网技术的不断发展&#xff0c;网络安全问题日益受到重视。其中&#xff0c;SSL证书作为网站安全的基石&#xff0c;其重要性不言而喻。SSL证书能够加密网站与用户之间的通信&#xff0c;保护用户隐私&#xff0c;防止信息被窃取和篡改。因此&#xff0c;越来越多的网站…

10.1作业

#include <stdio.h> #include <string.h> #include <stdlib.h> /** function:* 创建一个双向链表&#xff0c;将26个英文字母通过头插的方式插入&#xff0c;通过为尾删的方式读取并删除* param [ in] * param [out] * return */ typedef struct double…

ECharts多个数据视图进行自适应大小的解决方案

项目场景&#xff1a; 在制作数据视图时经常会遇到多个数据视图的情况&#xff0c;在多个数据视图的情况下做自适应是比较麻烦的&#xff0c;这里就详细的分析一下该如何去制作&#xff0c;分享一下我的解决办法及思路。 定义 DOM 容器 这里需要注意一个地方&#xff0c;在定…

《Jetpack Compose从入门到实战》第八章 Compose页面 导航

添加依赖&#xff1a;implementation “androidx.navigation:navigation-compose:$nav_version” Navigation for Compose class MainActivity : AppCompatActivity() {var theme: BloomTheme by mutableStateOf(BloomTheme.LIGHT)override fun onCreate(savedInstanceState:…

Vue3核心源码解析 (一) : 源码目录结构

通过软件框架阅读源码可以对框架本身运行机制进行学习&#xff0c;更能了解框架的API设计、原理及流程、设计思路&#xff1b;我们要知其然&#xff0c;更知其所以然。 Vue 3的源码相对于Vue 2版本有了较大程度的改变&#xff0c;采用Monorepo规范的目录结构&#xff0c;同时使…

JavaScript——APIs

复习&#xff1a; splice() 方法用于添加或删除数组中的元素。 **注意&#xff1a;**这种方法会改变原始数组。 删除数组&#xff1a; splice(起始位置&#xff0c; 删除的个数) 比如&#xff1a;1 let arr [red, green, blue] arr.splice(1,1) // 删除green元素 consol…

【ArcGIS Pro二次开发】(69):使用MapTool实现隐藏和隔离图层

一、MapTool简介 在ArcGIS Pro SDK中&#xff0c;MapTool是一个重要的组件&#xff0c;用于自定义地图操作工具&#xff0c;使用户能够在ArcGIS Pro中执行特定的地图交互操作。 在VS中添加新项&#xff0c;可以找到ArcGIS Pro 地图工具&#xff0c;即为MapTool。 新建后打开c…

全连接网络实现回归【房价预测的数据】

也是分为data&#xff0c;model&#xff0c;train&#xff0c;test import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optimclass FCNet(nn.Module):def __init__(self):super(FCNet,self).__init__()self.fc1 nn.Linear(331,200)s…

​萝卜刀《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书

​萝卜刀《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书 ​萝卜刀《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书

mysql join语句优化实战

生产环境的大表join语句性能经常很差。这里给出大表join语句的优化思路。 准备材料 两张表&#xff0c;t1表N行&#xff0c;t2表M行 CREATE TABLE identity.t1 (id INT NOT NULL COMMENT Id,a INT NULL,PRIMARY KEY (id));CREATE TABLE identity.t2 (id INT NOT NULL COMMEN…

react create-react-app 配置less

环境信息&#xff1a; create-react-app:v5 react:18.2.0 node:18.16.0 如果你不必须使用 less 建议直接使用scss。 因为less配置会遇到很多问题。 配置less过程&#xff1a; 如果你只需要 sass的话&#xff0c;就可以直接使用sass。因为默认配置了scss。 npm、yarn、cnpm、…

wordpress搭建自己的博客详细过程以及踩坑

WordPress作为一款开源的内容管理系统&#xff08;CMS&#xff09;&#xff0c;具有诸多优势。首先&#xff0c;它的易用性使得即使对于没有编程经验的用户来说也能轻松上手&#xff0c;通过直观的用户界面和友好的管理工具&#xff0c;用户可以方便地创建、编辑和发布内容。其…

Mac 上没有 Total Commander,可以用这两款软件来代替

在 Windows 上&#xff0c;我用的最多的文件管理软件是 Total Commander&#xff0c;但转到 macOS 上却没有一款和它功能一样的软件&#xff0c;让人有些不爽。 经过一番搜寻&#xff0c;终于找到了 2 款可以部分代替 Total Commander 的软件&#xff0c;在此与大家分享。 1、…

竞赛选题 大数据疫情分析及可视化系统

文章目录 0 前言2 开发简介3 数据集4 实现技术4.1 系统架构4.2 开发环境4.3 疫情地图4.3.1 填充图(Choropleth maps)4.3.2 气泡图 4.4 全国疫情实时追踪4.6 其他页面 5 关键代码最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 大数据疫…

五、2023.10.1.C++stl.5

文章目录 65、请说说 STL 的基本组成部分?66、请说说 STL 中常见的容器&#xff0c;并介绍一下实现原理&#xff1f;67、请说说 STL 中常见的容器&#xff0c;并介绍一下实现原理&#xff1f;68、请你来介绍一下 STL 的空间配置器&#xff08;allocator&#xff09;&#xff1…

LabVIEW开发光学相干断层扫描系统

LabVIEW开发光学相干断层扫描系统 癌症是一种以异常或受损细胞无法控制生长为特征的疾病&#xff0c;是世界上导致死亡的主要原因之一。以前的研究人员已经表明&#xff0c;患病时组织力学会发生变化。能够同时量化和可视化组织力学和细胞行为有可能弥合我们对这两种癌症驱动特…

地理空间探测器保姆级教程-含实现程序-少理论多操作

1.实现软件excel程序 【传送门】 2.基本步骤 2.1 准备的数据样式 按省份地理位置&#xff0c;分为东中西三大板块 2.2 数据离散化 数据不需要标准化、归一化等X&#xff08;X可能多个&#xff09;数据&#xff0c;需要离散化&#xff0c;eg&#xff0c;使用门槛值&#x…

SLAM从入门到精通(tf的使用)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 在ros的机器人学习过程中&#xff0c;有一件事情是肯定少不了的。那就是坐标系的转换。其实这也很容易理解。假设有一个机器人&#xff0c;它有一个…