当前位置: 首页 > article >正文

【Linux进程通信】————匿名管道命名管道

前言

没有人是一座孤岛——约翰·多恩布道词

  这句话告诉我们,没有人是孤独的,我们既然存在于世,必定会与他人产生联系,与他人交流,人不能脱离人活下去,不能脱离社会,与世隔绝。

  一个人的知识,力量是有限的,当我们遇到一个人无法解决的难题时,我们就需要与他人合作,或者依靠交流得到解决办法,克服难题。

  同样,在编程中,一个进程也会遇到无法解决的问题,这个时候,我们就需要与其他进程进行交流,合作,去解决问题。

  这就是今天的介绍主题——进程通信!

进程通信

1.1进程通信目的

进程通信目的:

  • 数据传递:一个进程需要将数据传递给别的进程
  • 资源共享:多个进程直接共享相同资源
  • 数据共享:多个进程共享相同数据
  • 通知事件:一个进程向另一个进程发送相关消息,使得另一进程方便做出相应反应。
  • 进程控制:一个进程想要控制另一个进程来完成某些任务,典型的有:Debug进程

无论怎么样,它们的最终目的都是为了让进程之间相互协作,共同处理解决问题。


1.2进程通信方式

1. 匿名管道(Pipe)

  • 定义:匿名管道是一种半双工的通信方式,它可以在两个进程之间传递数据。管道的特点是数据只能单向流动。

  • 特点:通常只用于具有亲缘关系的进程之间进行通信,例如父子进程之间。

2. 命名管道(Named Pipe)

  • 定义:命名管道与管道类似,但是它可以在不具有亲缘关系的进程之间进行通信。

  • 特点:命名管道具有一个唯一的名称,可以在文件系统中进行访问。

3. 信号(Signal)

  • 定义:信号是一种异步通信方式,它允许一个进程向另一个进程发送一个信号。

  • 特点:通常用于处理异步事件,例如键盘中断、终端关闭等。信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据。

4. 共享内存(Shared Memory)

  • 定义:共享内存是一种高效的进程通信方式,它允许多个进程访问同一块物理内存,从而实现数据共享。

  • 特点:速度快,但是需要处理并发访问和同步问题。

5. 信号量(Semaphore)

  • 定义:信号量是一种进程间同步和互斥的机制,它可以用于控制进程对共享资源的访问。

  • 特点:通过P操作和V操作对信号量进行操作,以实现进程间的同步和互斥。.

6. 消息队列(Message Queue)

  • 定义:消息队列是一种进程间通信方式,它允许进程之间传递消息。消息队列通常用于进程之间传递结构化的数据。

  • 特点:独立于发送进程和接受进程而存在,可以实现双向通信。


管道 

如图就是现实生活中存在的管道,常用于一个地方向另一个地方输送液体或者气体。

在Linux中,我们也有类似这样的结构,就是进程之间通信的管道,它是一个进程向另一个进程传送数据的媒介。

匿名管道

匿名管道是什么?

  • 一种将数据从一个进程传递到另一个进程的半双工进程通信方式

匿名管道的创建

#include<unistd.h>

int pipe(int pipe[2])
  • 功能:创建一个管道
  • 返回值:如果创建成功返回0,否则返回一个错误码
  • 参数:int pipe[2]是一个输出型参数,函数结束后会返回两个文件描述符,存储在数组pipe中,其中pipe[0]表示读端,pipe[1]表示写端。

匿名管道的使用

 匿名管道通信原理

  • 两个进程通过相同的文件描述符表访问同一文件。

   匿名管道特性:只能用于具有血缘关系的进程之间通信

  •   因为匿名管道是利用相同的文件描述符表进行通信的,而进程具有独立性,进程的文件描述符表也是独立的,不同的两个进程的文件描述符表也一定是不同的。
  •   父子进程因为继承关系,子进程的文件描述表和父进程相同,所以匿名管道只能用于具有一定血缘关系的进程通信。

过程

       1.父进程使用pipe函数创建一个管道。

          pipe函数完成后,创建管道成功,得到两个文件描述符,一个指向管道读端,一个指向管道写端。

         

        2.父进程创建子进程。

           通过继承关系,子进程继承父进程的文件描述符表,这样就使得两个不同进程拥有同一张文件描述符表,得到pipe返回的两个文件描述符,同样使子进程也可以指向管道的读段,管道的写端。

       

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

           形成单向通信,一端读,一端写,使数据流单向流通,至此,匿名管道建设完成。

       

代码实现:

​
#include<iostream>
#include<unistd.h>
#include<string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
#define MAX  1024
int main()
{
    int pipefd[2]={0};
    int n=pipe(pipefd);
    if(n==0)
    {
        cout<<"创建管道成功"<<endl;
        cout<<"pipefd[0]: "<<pipefd[0]<<" pipefd[1]: "<<pipefd[1]<<endl;
    }
    pid_t id=fork();
    //子进程
    if(id==0)
    {
        //关闭写端
        close(pipefd[0]);
        int num=0;
        while(true)
        {
            char child_buff[MAX];
            snprintf(child_buff,sizeof(child_buff),"hello father ,i am a child process ,my pid is %d,num is %d\n",getpid(),num);
            size_t n=write(pipefd[1],child_buff,strlen(child_buff));
            sleep(1);
            num++;
        }
    }
    //父进程,关闭写端
    close(pipefd[1]);
    char father_buff[MAX];
    while(true)
    {
        size_t n=read(pipefd[0],father_buff,sizeof(father_buff)-1);
        if(n>0)
        {
            father_buff[n]=0;
            cout<<"child say:"<<father_buff<<endl;
        }
        else if(n==0)
        {
            return 0;
        }
        sleep(1);
    }
    return 0;
}

​

匿名管道本质

  匿名管道是利用文件描述符表进行通信的,那匿名管道是文件吗?

  •   实际并不是,深度学习后会发现,匿名管道仅仅只是内存上的一块缓冲区,内核通过维护这个缓冲区,用这个缓冲区来实现父子进程通信。

  为什么要这样做呢?

  • 进程通信的本质是让不同进程看到同一份资源,看到同一份缓冲区也是同一份资源,并且缓冲区存在于内存之上,不占用磁盘空间,节省了计算机空间资源。
  • 缓冲区资源在进程结束后,会被自动回收。

  所以,匿名管道的本质就是一个存在于内存上的缓冲区

命名管道

什么是命名管道?

  • 当匿名管道被冠以名字,就成为了命名管道,命名管道不在仅仅是内存上一块随用随到,不用就回收的一段内存,而是真实存在于磁盘文件管理系统当中,可以被不同进程打开。

 命名管道拥有名字后,不再受血缘关系的限制,不在拘泥于父子进程,兄弟进程,而是任何不同的两个进程,或者多个进程,都可以通过其完成通信,因为命名管道有了名字,任何进行都可以通过这个唯一的标识符寻找它,打开它,操作它,通过它实现通信。

命名管道创建

命令行指令:mkfifo  pipe_name

功能:在当前目录下创建一个管道文件

示例:

每种文件都有自己的标签,一般普通文件的标签是-,而管道文件是p


在程序中,我们使用系统调用接口mkfifo来创建管道文件

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

返回值:创建成功返回0,否则返回-1

参数1:文件路径

参数2:标志位,文件权限(受系统掩码限制)

命名管道的使用

命名管道原理:不同进程通过打开同一文件,操作同一缓冲区进行通信。

1.当进程打开磁盘文件的时候,操作系统内核会为其分配对应的内核数据结构struct file,这个struct file结构体保存文件的属性,缓冲区指针等。

2.于此同时,当另一个进程也打开这个磁盘文件的时候,操作系统并不会再为其创建一个struct file结构体,而是将上次创建的结构体一起给他使用,struct file结构体中有一个引用计数器ref,代表有几个进程使用它。

3.两个进程打开同一文件,使用同一个struct file结构体,通过struct file结构体的管理缓冲区的指针,两个进程就能同时操作一个缓冲区,达到了让不同进程看到同一资源的目的。

代码实现

头文件 namepipe.hpp

#include<iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include <stdio.h>


using namespace std;
const char filename[20]="pipe";

服务端 Serve.cc

#include <iostream>
#include "namepipe.hpp"
using namespace std;
#define Max 1024
bool Creatpipe()
{
    int n = mkfifo(filename,0666 );
    if (n == 0)
    {
        cout << "创建管道成功" << endl;
        return true;
    }
    else
    {
        cout << "创建管道失败" << endl;
        return false;
    }
}
int main()
{
    if (Creatpipe())
    {
        int pfd = open(filename, O_RDONLY);//打开文件,获取文件描述符
        while (true)//读端
        {
            char serverbuff[Max];
            int n = read(pfd, serverbuff, sizeof(serverbuff) - 1);
            if (n > 0)//缓冲区有数据就读,无数据,读到‘ 0 ’退出
            {
                serverbuff[n] = 0;//将最后一个元素置为0,更加安全
                if (strcmp(serverbuff, "end") == 0)
                {
                    cout<<"结束服务"<<endl;
                    break;
                }
                else
                {
                    cout << "Client say:" << serverbuff << endl;
                }
            }
            else
            {
                break;
            }
        }
    }
    return 0;
}

客户端 Client.cc

#include"namepipe.hpp"
int main()
{
    int pfd=open(filename,O_WRONLY);
    char clientbuff[1024];
    while(true)
    {
        cout<<"#Please write:";
        cin>>clientbuff;
        ssize_t w=write(pfd,clientbuff,strlen(clientbuff)+1);
    }
}

结果

QQ2024915-14740

命名管道的本质

当在文件系统中查看文件的时候,我们也已经发现

命名管道文件的大小为零,也就是没有大小,不占用数据块,代表它是一个内存级文件

为什么呢?

  • 命名管道只是为了让不同进程之间进行通信,随着进程的生命周期而消亡,进程通信结束,它也就没有用了,所以不必为它分配数据块。
  • 我们要用的只有命名管道文件被打开时,内核操作系统为它分配的缓冲区,这个缓冲区的数据不会更新到磁盘当中,如果内存为他分配了数据块,还会进行刷盘操作,降低效率。
  • Linux操作系统将其设置为内存级文件,大小始终为零。
     

所以,命名管道本质上是一个内存级文件,大小为零。

管道通信的四种情况

当进程之间通过管道通信的时候,因为读写速度,读写端生命周期不一样,会出现很多问题

其大致可以归类为四种

  • 读快,写慢,当管道缓冲区的数据被读段读取完了,这时候读端会进入阻塞状态,写段继续向缓冲区写入数据。
  • 写慢,读快,当管道缓冲区被写端写满,写端无法继续写,进入阻塞状态,读端继续从缓冲区中读取数据。
  • 读端先关闭,写端一直写入,OS会主动终止写端进程。
  • 写端先关闭,读端一直读取,直到read的返回值为零,代表读到文件结尾。

管道的特点

管道的特点

  • 半双工,数据只能从一个进程到另一个进程流动,单次通信,方向是单向的。
  • 写入管道的数据遵循先入先出规则,最早被写入的数据最先被读出。
  • 传输的数据是无格式的字节流。
  • 默认给读写端提供同步机制。
  • 管道的生命周期是随进程的。

 寄语

本文章对进程的管道通信进行了阐述,希望其中的一些观点对大家的学习有一定帮助,其中也会有不足的地方,希望大家能够指出,在评论区讨论。

大鹏一日通风起,扶摇直上九万里——李白


http://www.kler.cn/a/587983.html

相关文章:

  • Matlab 风力发电机磁悬浮轴承模型pid控制
  • 从需求文档到智能化测试:基于 PaddleOCR 的图片信息处理与自动化提取
  • 每日Attention学习28——Strip Pooling
  • CVPR2024 | TT3D | 物理世界中可迁移目标性 3D 对抗攻击
  • day04_Java高级
  • 练习题:89
  • 考研专业课复习方法:如何高效记忆和理解?
  • 应用于电池模块的 Fluent 共轭传热耦合
  • C++学习笔记(二十)——类之运算符重载
  • 代码随想录算法训练营第32天 | 509. 斐波那契数 70. 爬楼梯 746. 使用最小花费爬楼梯
  • 【数据分析】读取文档(读取Excel)
  • Varjo:为战场各兵种综合训练提供XR技术支持
  • DeepSeek-R1大模型微调技术深度解析:架构、方法与应用全解析
  • 【论文阅读】Cross-View Fusion for Multi-View Clustering
  • Flash Attention原理讲解
  • 【Linux】:socket编程——UDP
  • 传输层tcp/udp
  • 287. 寻找重复数
  • Python实现万年历
  • DAY34 贪心算法Ⅲ