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

进程间的通信(IPC)基础了解,匿名管道使用,有名管道使用

进程间通信基本知识

进程间通信的定义

进程间通信方式分类

匿名管道(pipe)

匿名管道介绍

  • 创建方式:使用 pipe 系统调用创建,返回一对文件描述符(读端和写端)。
  • 生命周期:匿名管道的生命周期与创建它的进程有关。当进程结束时,匿名管道会被销毁。
  • 作用域:通常用于有亲缘关系的进程(如父子进程)之间的通信。

匿名管道常见用法

  1. 创建管道pipe(pipefd),pipefd是我们自定义的一个数组,将数组传递给pipe函数是为了存储pipe创建匿名管道时提供的两个文件描述符,我们的pipefd 数组中的 pipefd[0] 是读端文件描述符,pipefd[1] 是写端文件描述符。
  2. 写入数据write(pipefd[1], data, size),将数据写入管道。
  3. 读取数据read(pipefd[0], buffer, size),从管道读取数据。
  4. 关闭管道:使用 close(pipefd[0])close(pipefd[1]) 关闭读端和写端,释放资源。

创建匿名管道看起来似乎用到的是管道文件来进行通讯,但实际上并不是的,这里和管道文件时没有什么关联的,管道文件是真实存在于文件系统中的一种类型文件,我们可以用ls,rm这些命令对其进行操作。

匿名管道通过操作系统内核维护一个内存缓冲区一对文件描述符(分别对应于管道的读端和写端)来实现进程之间的通信。当你创建一个匿名管道时,操作系统内核会分配和管理这个内存缓冲区,以支持数据的传输。数据从写端写入缓冲区,另一个进程从读端读取数据。虽然这里的缓冲区不是文件,但是我们可以将匿名管道视作一个文件来对其进行操作,使用read,write,和两个文件描述符来对文件进行读写

匿名管道的特点

亲缘关系通常用于有亲缘关系的进程(如父子进程)之间的通信。管道的读端和写端在父进程和子进程之间共享文件描述符。

为什么使用匿名管道通信的进程必须是亲缘关系???请看下面图文解释:

  • 当一个进程通过 pipe() 系统调用创建匿名管道时,内核会返回两个文件描述符(一个用于读,一个用于写,对应匿名管道的读端和写端)。这些文件描述符是进程在内核中访问管道的唯一标识。
  • 继承机制:在UNIX和Linux系统中,当一个进程通过 fork() 创建子进程时,子进程会继承父进程的所有文件描述符。这意味着子进程可以使用与父进程相同的文件描述符来访问管道,从而实现进程间的通信。
  • 无文件系统接口:匿名管道不会在文件系统中创建实际文件,所以只能依靠文件描述符来访问。如果进程之间没有亲缘关系(如没有使用 fork()),则它们无法直接共享这些文件描述符。

 一对一通信每个匿名管道只能实现两个进程之间的单向通信。

        匿名管道的设计本质上是一种“点对点”通信方式,即两个进程之间直接传输数据。虽然可以让多个进程共享匿名管道的文件描述符,但这会导致数据读取的竞争或写入的混乱,所以并不推荐这样使用。

         单向通信匿名管道是半双工的,这意味着数据只能在一个方向流动。你需要两个匿名管道来实现双向通信,其中一个用于从进程A到进程B的数据流,另一个用于从进程B到进程A的数据流。

        匿名管道确实是半双工的,这意味着在给定时间内,数据只能在一个方向上流动。然而,半双工并不禁止你在不同的时间段内切换通信方向——你可以在一个时间段内让进程A写、进程B读,之后再让进程B写、进程A读。

        尽管切换通信方式在理论上可行,但在实际开发中,由于其复杂性和潜在的同步问题,通常不会这样操作。更直观、简单的方法是使用两个管道或选择其他更合适的IPC机制

匿名管道的使用

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>int main(void)
{// 创建匿名管道int fd[2];//保存匿名管道的两个文件描述符pipe(fd);pid_t pid=fork();// 子进程if(pid == 0){// 向父进程打招呼char *msg = "hello parent!";//sleep(2);write(fd[1], msg, strlen(msg));exit(0);}// 父进程if(pid > 0){char buf[50];bzero(buf, 50);// 静静地等待子进程的消息read(fd[0], buf, 50);printf("来自子进程: %s\n", buf);exit(0);}
}

看到这个代码和结果你可能觉得是偶然,确实,由于父进程和子进程并发执行,前提是子进程先从匿名管道写端写入数据,父进程才能正确读到并打印子进程传输的消息。

所以这里不妨让子进程在写之前sleep(2);结果你会发现父进程仍然成功收到并且打印出了子进程传递信息,只不过是延迟了两秒,证明父进程被阻塞等待子进程传递信息过来,接下来让我们详细说一下匿名管道的读写规则

匿名管道的读写规则

操作受到阻塞的情况(2)

读写端正常,若管道如果为空,读端就要阻塞;
读写端正常,若管道如果被写满,写端被阻塞;

操作正常执行的情况(4)

读写端正常,读的时候,管道中有数据;
读写端正常,写的时候,管道中未被写满;
读端正常,无写端,读的时候,管道中有数据;
读端正常,无写端,读的时候,管道中无数据,这个read操作返回0,说明管道缓冲区数据读完了,也算read正常执行;

操作不正常的情况(2)

只要没有读端,任何试图写入管道的操作都会触发 SIGPIPE 信号,而不论管道的缓冲区是否已满。

匿名管道无用的文件描述符及时关闭

一般而言,不需要用到的文件描述符都最好及时关闭,避免不必要的副作用或浪费系统资源。例如上述程序中,子进程只用到了管道的写端,因此它的fd[0]可以也应该要关闭,相反父进程只用到了管道的读端,因此它的fd[1]可以也应该关闭。代码可以改成:

int main(void)
{
    // 创建匿名管道
    int fd[2];
    pipe(fd);

    // 子进程
    if(fork() == 0)
    {
        // 关掉不必要的读端
        close(fd[0]);

        ...
    }

    // 父进程
    else
    {
        // 关掉不必要的写端
        close(fd[1]);

        ...
    }
}

如果我们需要实现双向通信,则最好使用两个管道,然后将里面不必要的文件描述符都及时关闭。 

命名管道(fifo)


http://www.mrgr.cn/news/17459.html

相关文章:

  • 9-8 束搜索
  • 栈和队列的学习以及实现+双端队列的底层原理
  • wiki.js 部署
  • 华为云征文 | Flexus X与宝塔面板的完美结合,让云管理更轻松
  • 读书闲思---2024.09.01
  • nginx部署前端vue项目步骤
  • Java网络编程概述
  • python 笔记 geo-bleu
  • OpenSetting组件的用法
  • 【秋招笔试】9.01字节跳动秋招(已改编)-三语言题解
  • vercel免费在线部署TodoList网页应用
  • C++之搜索二叉树(上)
  • docker实战扩展四( Dockerfile 中,COPY . .详细讲解)
  • AI时代,需要什么样的服务器操作系统?
  • Fine-Grained Egocentric Hand-Object(中文翻译)
  • 【SpringMVC】
  • CAN总线简介
  • 《高等代数》三对角行列式的递推法
  • 【教学类-35-21】20240901 中2班描字帖(学号+姓名、虚拟姓名、杨任东竹石体 Regular)
  • Auto-Unit-Test-Case-Generator -- java项目自动测试生成