【Linux】进程间通信——管道(1w5字图文详解,附代码和运行动图)

目录

一、进程间通信

1.1 概念

1.2 手段

二、管道

2.1 概念

2.2 匿名管道

2.3 管道的特征

2.4 管道的读写规则

2.5 管道的应用场景

三、命名管道

3.1 创建命名管道

3.2 使用命名管道

3.3 命名管道的读写规则


一、进程间通信

1.1 概念

进程间通信(Inter-Process Communication,IPC)是指在不同的进程之间共享信息或数据,实现数据层面的交互。

我们知道,进程具有独立性,默认情况下两个进程是无法进行信息交流的,因此我们需要某些技术手段来让不同进程之间能够共享信息

进程间通信的本质,就是让两个进程能够看到同一份“资源”,这份资源一般由操作系统提供,因此进程访问这份资源进行通信,本质上就是在访问操作系统,所以要实现进程间通信,我们需要调用对应的系统调用接口。

操作系统中的两大IPC模块定制标准:System V和Posix,为我们提供了一系列进程间通信方法

1.2 手段

常见的进程间通信手段主要有:

  • 匿名管道(Pipe):适用于父子进程间通信,只能单向传输数据,没有同步机制
  • 命名管道(Named Pipe):可用于任意两个进程间的通信
  • 消息队列(Message Queue):允许进程将消息发送到队列中,其他进程可以从队列中读取这些消息
  • 信号(Signal):最古老的进程间通信的方法之一,用于通知进程发生了某个事件
  • 共享内存(Shared Memory)在内存中创建一个共享区域,并让多个进程能够访问该区域
  • 套接字(Socket):适用于需要进行网络通信的进程
  • 信号量(Semaphore):主要用于多进程、多线程之间的同步互斥问题


二、管道

2.1 概念

管道是Linux中的一种进程间通信方式

我们可以把管道想象成连接两个进程间的一条管子,一个进程的数据流就能通过这个管子流向另一个进程。

管道分为匿名管道pipe命名管道FIFO两种,通常我们所说的管道指匿名管道,二者除了创建、使用等方式不同,原理是相同的,都是通过内核的一块缓冲区实现数据传输

2.2 匿名管道

匿名管道(pipe)是一个临时创建的对象

站在文件描述符角度来看,我们可以把两个进程的文件描述符分别指向管道的读端和写端,一个进程向管道中写,另一个进程从管道中读,就实现了进程间通信。

站在内核角度来看,管道的本质就是两个file结构体(一个用于写一个用于读)、一个临时创建的inode节点加上一个内存的物理页。进程向管道中写入时,数据被写入到了这个共享数据页中;进程从管道中读取时,数据又从这个页中被拷贝出来

理解了管道的本质,我们就理解了为什么管道只能进行单向通信

创建匿名管道的接口:

#include <unistd.h>
int pipe(int fd[2]);

该函数的参数是一个输出型参数,我们需要向函数内传入一个大小为2,元素类型为int的数组。匿名管道创建完毕后,传入的数组内部会存放读端和写端的文件描述符,其中fd[0]为读端,fd[1]为写端

创建匿名管道成功,函数会返回0,创建失败返回-1并设置errno

我们可以用一段简单的代码来验证一下:

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

using namespace std;

int main()
{
    int fd[2] = {0};
    int n = pipe(fd);
    if(n < 0)
        return 1;
    cout << "fd[0]=" << fd[0] << " fd[1]=" << fd[1] << endl;
    return 0;
}

运行结果:

我们知道,进程的前三个文件描述符分别被标准输入流、标准输出流和标准错误流占用,所以创建匿名管道时文件描述符只能从3开始,符合预期

但匿名管道又是如何实现父子进程间通信的呢?

通过fork创建子进程后,子进程会继承父进程的各种信息,其中就包括文件描述符表,因此父进程如果在创建子进程时就已经创建了匿名管道,后续子进程的文件描述符也会与该管道对应

 

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

using namespace std;

int main()
{
    int fd[2] = {0};
    int n = pipe(fd); //父进程建立匿名管道
    if(n < 0)
        return 1;
    pid_t id = fork(); //创建子进程
    if(id < 0)
        return 2;
    return 0;
}

父进程向子进程通信的情况,再将父进程指向管道读端的文件描述符关闭,子进程指向管道写端的文件描述符关闭。如果要实现子进程向父进程通信,则反过来即可

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

using namespace std;

int main()
{
    int fd[2] = {0};
    int n = pipe(fd); //父进程建立匿名管道
    if(n < 0)
        return 1;
    pid_t id = fork(); //创建子进程
    if(id < 0)
        return 2;
    if(id == 0) //子进程
    {
        close(fd[1]); //关闭写端
        //开始通信
        //...
        close(fd[0]); //通信完毕
        exit(0);
    }
    //父进程
    close(fd[0]); //关闭读端
    //开始通信
    //...
    close(fd[1]); //通信完毕
    return 0;
}

所以,通过匿名管道实现进程间通信的前提,是两个进程间有血缘关系(文件描述符可被继承)

匿名管道不需要将数据拷贝到磁盘中,属于内存级文件,没有路径、文件名和inode,因此而得名

以上就是使用匿名管道实现进程间通信的前置操作——建立通信信道,接下来我们才开始真正的进程间通信

文件描述符也有了,我们只需要让父子进程一个向管道写入内容,一个从管道读取内容,就可以完成通信了。这里用一段简单的代码来验证:

#include <iostream>
#include <string>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

using namespace std;

void Writer(int wfd)
{
    //随便准备一些用于通信的内容
    string s = "hello, I am father";
    pid_t self = getpid();

    char buffer[1024];
    while(true)
    {
        buffer[0] = 0; //清空字符串
        snprintf(buffer, sizeof(buffer), "%s, pid:%d", s.c_str(), self); //将内容格式化输入到目标字符串中
        write(wfd, buffer, strlen(buffer)); //将字符串写入管道
        sleep(1);
    }
}

void Reader(int rfd)
{
    char buffer[1024];
    while(true)
    {
        buffer[0] = 0;
        ssize_t n = read(rfd, buffer, sizeof(buffer)); //从管道读取内容
        if(n > 0)
        {
            buffer[n] = 0; //这里的0相当于'\0'
            cout << "child get a massage[" << getpid() << "]#" << buffer << endl; //打印读取到的内容
    
评论 34
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值