【Linux】进程通信之管道通信详解

news2024/12/23 0:51:13

🍎作者:阿润菜菜
📖专栏:Linux系统编程


一、什么是管道通信

1. 管道通信是一种在进程间传递数据的方法

其实管道通信是Unix中最古老的进程间通信的形式了:

  1. 管道通信是一种进程间通信的方式,它可以让一个进程的输出作为另一个进程的输入,实现数据的传输、资源的共享、事件的通知和进程的控制。

  2. 管道通信分为两种类型:匿名管道和命名管道。

  3. 匿名管道是只能在父子进程间使用的,它通过pipe()函数创建,并返回两个文件描述符,一个用于读,一个用于写。

  4. 命名管道是可以在任意进程间使用的,它通过mkfifo()或mknod()函数创建一个特殊的文件,然后通过open()函数打开,并返回一个文件描述符,用于读或写。

  5. 管道通信的特点是面向字节流、占用内存空间、只能单向传输、有固定的大小和缓冲区等。

在这里插入图片描述

2.看看接口:匿名管道和命名管道

pipe()函数是用来创建一个匿名管道的,它的原型是:

#include <unistd.h>
int pipe(int pipefd[2]); // 返回值:若成功返回0,失败返回-1

pipe()函数会返回两个文件描述符,pipefd[0]用于读取管道中的数据,pipefd[1]用于向管道中写入数据。匿名管道只能在具有亲缘关系的进程间通信,通常是父子进程或兄弟进程。
在这里插入图片描述

mkfifo()函数是用来创建一个命名管道的,它的原型是:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode); // 返回值:成功返回0,出错返回-1

mkfifo()函数会在文件系统中创建一个特殊的文件,该文件用于提供FIFO功能,即命名管道。命名管道可以在无关的进程间通信,只要知道它的路径名。命名管道需要用open()函数打开,并返回一个文件描述符,用于读或写。

pipe()函数和mkfifo()函数的区别主要有以下几点:

  • pipe()函数创建的管道是匿名的,只能在有亲缘关系的进程间通信;mkfifo()函数创建的管道是有名字的,可以在任意进程间通信。
  • pipe()函数创建的管道是在内存中的,不占用磁盘空间;mkfifo()函数创建的管道是在文件系统中的,占用磁盘空间。
  • pipe()函数创建并打开了管道,返回两个文件描述符;mkfifo()函数只创建了管道,需要用open()函数打开,并返回一个文件描述符。
  • pipe()函数创建的管道默认是阻塞的,即读写操作会等待对方进程;mkfifo()函数创建的管道可以指定非阻塞标志(O_NONBLOCK),即读写操作会立即返回成功或失败。

3. 管道通信的本质是什么?

管道通信的本质是利用内核提供的一块缓存区来实现不同进程间的数据传输、资源共享、事件通知和进程控制。

管道通信分为匿名管道和命名管道,它们都是一种特殊的文件,可以用普通的文件I/O函数进行操作。

1.匿名管道是通过pipe()函数创建并返回两个文件描述符,一个用于读,一个用于写。匿名管道只能在具有亲缘关系的进程间通信,通常是父子进程或兄弟进程。

2.命名管道是通过mkfifo()函数或mknod()函数创建一个特殊的文件,并通过open()函数打开并返回一个文件描述符,用于读或写。命名管道可以在任意进程间通信,只要知道它的路径名

3.无论是匿名管道还是命名管道,它们都使用了环形缓冲区来存储数据。环形缓冲区是由16个内存页成的,每个内存页有一个pipe_buffer对象来管理。环形缓冲区有一个读指针和一个写指针来记录读写操作的位置。

4.当向管道写入数据时,从写指针指向的位置开始写入,并且将写指针向前移动。而从管道读取数据时,从读指针开始读入,并且将读指针向前移动。当对没有数据可读的管道进行读操作,或者对没有空闲空间的管道进行写操作时,会阻塞当前进程,除非设置了非阻塞标志(O_NONBLOCK)。

二、管道通信的实现和深入理解

1.如何创建和使用匿名管道

一般步骤如下:

  • 在父进程中,使用pipe()函数创建一个匿名管道,该函数会返回两个文件描述符,一个用于读,一个用于写。
  • 在父进程中,使用fork()函数创建一个子进程,子进程会继承父进程的文件描述符,从而可以访问同一个管道。
  • 在父进程或子进程中,根据需要关闭不使用的管道端,比如父进程只写,子进程只读。
  • 在父进程或子进程中,使用普通的文件I/O函数(如write()、read()、printf()、scanf()等)来向管道写入或读取数据。
  • 在父进程或子进程中,使用close()函数关闭管道端。
    在这里插入图片描述

一个示例代码:

// 父进程向子进程发送字符串
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define STRING "hello world!"
#define BUFSIZ 1024
int main()
{
    int pipefd[2]; // 管道文件描述符
    char buf[BUFSIZ]; // 缓冲区
    char *msg = STRING; // 消息

    // 创建匿名管道
    if (pipe(pipefd) == -1)
    {
        perror("pipe()");
        exit(1);
    }

    // 创建子进程
   pid_t pid = fork();
    if (pid == 0) // 子进程
    {
        close(pipefd[1]); // 关闭管道写端
        if (read(pipefd[0], buf, BUFSIZ) < 0) // 从管道读端读取数据
        {
            perror("read()");
            exit(1);
        }
        printf("%s\n", buf); // 打印数据
        exit(0);
    }
    else // 父进程
    {
        close(pipefd[0]); // 关闭管道读端
        if (write(pipefd[1], msg, strlen(msg)) < 0) // 向管道写端写入数据
        {
            perror("write()");
            exit(1);
        }
        wait(NULL); // 等待子进程结束
        //wait(NULL)函数会暂停当前进程的执行,直到有一个子进程终止或发生信号
    }
    return 0;
}

小问题

必须在创建子进程之前创建匿名管道才能实现父子进程通信吗?为什么?

必须的!

必须在创建子进程之前创建匿名管道的原因有以下几点:

  • 匿名管道是通过 pipe 系统调用来创建的,该系统调用会返回两个文件描述符,分别表示管道的读端和写端。
  • 子进程会继承父进程打开的文件描述符,包括管道的读端和写端。这样,父子进程就可以通过共享的文件描述符来访问同一个管道。
  • 如果在创建子进程之后再创建匿名管道,那么父子进程就无法共享文件描述符,也就无法通过同一个管道进行通信。

2.如何创建和使用命名管道

一般步骤如下

  • 在任意进程中,使用mkfifo()函数或mknod()函数创建一个特殊的文件,该文件用于提供命名管道的功能,需要指定一个路径名和一个权限模式
  • 在任意进程中,使用open()函数打开该文件,并返回一个文件描述符,用于读或写。可以指定非阻塞标志(O_NONBLOCK),以避免在没有对方进程时阻塞。
  • 在任意进程中,使用普通的文件I/O函数(如write()、read()、printf()、scanf()等)来向管道写入或读取数据。
  • 在任意进程中,使用close()函数关闭管道端。

这里再次理解一下进程阻塞

代码:

// 服务器进程向客户端进程发送字符串
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define FIFO_NAME "testfifo" // 命名管道路径名
#define STRING "hello world!" // 消息

int main()
{
    int pipefd; // 管道文件描述符
    char *msg = STRING; // 消息

    // 创建命名管道
    if (mkfifo(FIFO_NAME, 0666) == -1)
    {
        perror("mkfifo()");
        exit(1);
    }

    // 打开命名管道
    pipefd = open(FIFO_NAME, O_WRONLY);
    if (pipefd == -1)
    {
        perror("open()");
        exit(1);
    }

    // 向命名管道写入数据
    if (write(pipefd, msg, strlen(msg)) < 0)
    {
        perror("write()");
        exit(1);
    }

    // 关闭命名管道
    close(pipefd);

    return 0;
}

3.命名管道实现server&client通信

命名管道通信的大概原理如下:

  • 命名管道由mkfifo()系统调用或者mkfifo命令创建,它在文件系统中有一个路径名和一个inode节点,可以被不同的进程打开和读写。
  • 命名管道通信是以连接的方式进行的,服务器创建一个命名管道对象,然后在此对象上等待连接请求,一旦客户端连接过来,则两者都可以通过命名管道读或者写数据。
  • 命名管道通信是阻塞式的,即当一个进程试图从空管道读取数据时,它会被阻塞,直到有数据可读;当一个进程试图向满管道写入数据时,它也会被阻塞,直到有空间可写。
  • 命名管道通信是面向字节流的,数据在管道中先进先出(FIFO)。当一个进程向管道写入数据时,数据会被存放在内核缓冲区中,直到另一个进程从管道读取数据或者缓冲区满为止。

大志原理代码示例如下: 具体看我的代码仓库中的代码示例

  • 服务器端代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define MAX 1024

int main()
{
    if (mkfifo("./fifo", 0644) < 0) //创建命名管道,并判断是否创建成功
    {
        perror("mkfifo error!\n");
        return 1;
    }
    int fd = open("./fifo", O_RDONLY); //让./fifo以只读方式打开
    if (fd < 0)
    {
        perror("read error!\n");
        return 2;
    }
    char buf[MAX];
    while (1)
    {
        buf[0] = 0;
        printf("Please wait...\n");
        ssize_t s = read(fd, buf, sizeof(buf) - 1); //从管道中读取数据
        if (s > 0) //读取成功
        {
            buf[s - 1] = 0;
            printf("client# %s\n", buf);
        }
        else if (s == 0)
        {
            printf("client quit,server quit too!\n");
            break;
        }
        else
        {
            perror("read");
        }
    }
    close(fd); //关闭读端
    return 0;
}
  • 客户端代码:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define MAX 1024

int main()
{
    int fd = open("./fifo", O_WRONLY); //把./fifo文件以只写方式打开
    if (fd < 0)
    {
        perror("read");
        return 1;
    }
    char buf[MAX];
    while (1)
    {
        buf[0] = 0;
        printf("Please Enter:> ");
        scanf("%s", buf);
        write(fd, buf, strlen(buf)); //往管道里面写入数据
    }

4 再次理解管道通信!

站在文件描述符角度去深度理解管道

在这里插入图片描述

  1. 我们知道文件描述符是一个数字,用来表示进程打开的文件。每个进程都有一个文件描述符表,用来存储文件描述符和对应的文件指针。

  2. 管道是一种利用内核缓冲区实现进程间通信的方法,它可以让一个进程的输出作为另一个进程的输入,实现数据的单向传输。

  3. 管道由pipe()系统调用创建,返回两个文件描述符,分别代表管道的读端和写端。通常,一个进程创建管道后,再fork出一个子进程,然后父子进程分别关闭不需要的管道端,建立通信连接

站在内核角度看看管道本质

在这里插入图片描述
Again:管道通信是一种利用内核缓冲区实现进程间通信的方法,它可以让一个进程的输出作为另一个进程的输入,实现数据的单向传输
所以,看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想”!

5. 管道通信特点或者说工作机制

  • 管道通信是半双工的,有固定的读端和写端。
  • 管道通信是先进先出的,数据被进程从管道读出后,在管道中该数据就不存在了。
  • 管道通信是基于文件操作的,需要使用文件描述符来管理管道的读写。
  • 管道通信是阻塞式的,当进程去读取空管道或者写入满管道时,进程会阻塞
  • 管道通信分为匿名管道和命名管道,匿名管道只能用于具有亲缘关系的进程间通信,命名管道可以用于任何进程间通信。
  • 一般而言,进程退出,管道释放,所以管道的生命周期随进程
    一般而言,内核会对管道操作进行同步与互斥
  • 管道通信是面向字节流的,数据在管道中先进先出(FIFO)。当一个进程向管道写入数据时,数据会被存放在内核缓冲区中,直到另一个进程从管道读取数据或者缓冲区满为止。

在这里插入图片描述

三、管道通信的优化和问题

管道通信的优点有以下几点:

  • 管道通信是简单易用的,只需要使用系统调用 pipe 或 mkfifo 就可以创建一个管道文件,然后使用文件操作函数来读写数据。
  • 管道通信是安全的,匿名管道只能用于具有亲缘关系的进程间通信,命名管道可以通过文件权限来控制访问。
  • 管道通信是面向字节流的,不需要事先约定数据的格式,也不需要考虑字节序的问题。

管道通信的缺点有以下几点:

  • 管道通信是单向的,如果要实现双向通信,需要创建两个管道。
  • 管道通信是阻塞式的,如果读端没有数据可读或者写端没有空间可写,进程会被阻塞。
  • 管道通信是缓冲区有限的,如果写入数据过多而读出数据过少,会导致缓冲区满而无法继续写入。
  • 管道通信是不可靠的,如果读端或者写端被关闭,另一端可能会收到错误的信号或者返回值。

改进管道通信性能和效率的方法有以下几点:

  • 使用双向管道,可以实现两个进程之间的双向通信,而不需要创建两个单向管道。双向管道可以通过 socketpair 系统调用来创建,返回两个文件描述符,分别表示管道的两端。
  • 使用非阻塞模式,可以避免进程在读写管道时被阻塞,提高并发性能。非阻塞模式可以通过 fcntl 系统调用来设置文件描述符的 O_NONBLOCK 标志。
  • 使用自定义协议,可以根据通信的需求和场景,设计合适的数据格式和交互方式,提高数据传输的效率和可靠性。自定义协议可以包括数据包的长度、类型、校验码等信息。
  • 调整管道缓冲区的大小,可以根据数据量的大小和频率,选择合适的缓冲区大小,避免缓冲区溢出或者空闲浪费。管道缓冲区的大小可以通过 fcntl 系统调用来设置 F_SETPIPE_SZ 标志,并且可以通过 /proc/sys/fs/pipe-max-size 来修改最大容量。

在这里插入图片描述

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

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

相关文章

聚观早报|马斯克将TruthGPT挑战ChatGPT;腾讯披露自研芯片新进展

今日要闻&#xff1a;马斯克将TruthGPT挑战ChatGPT&#xff1b;苹果在印度年销售额近60亿美元&#xff1b;腾讯披露自研芯片沧海最新进展&#xff1b;特斯拉中国工厂普通工人月薪约1万元&#xff1b;飞猪将直接向阿里CEO张勇汇报 马斯克将TruthGPT挑战ChatGPT 4 月 18 日消息&…

Pytorch深度学习笔记(四)梯度向下算法

课程推荐&#xff1a;03.梯度下降算法_哔哩哔哩_bilibili 优化问题&#xff1a;求误差值函数最小的权重w &#xff08;1&#xff09;梯度向下算法思想 在绝大多数的情况下&#xff0c;损失函数是很复杂的&#xff08;比如逻辑回归&#xff09;&#xff0c;根本无法得到参数估计…

从输入url到页面展现(三)通过DNS将域名解析为IP地址以及dns-prefetch的好处

前言 上一节我们用直白的话讲了一下浏览器解析url&#xff0c;而浏览器并不具备发送网络消息的能力&#xff0c;所以委托操作系统发送&#xff0c;而这里的第一步&#xff0c;就是去找到对应web服务器的ip地址&#xff0c;并且对互联网和小子网有了一个认识。 这一节呢说一下我…

换个角度使用Redis去解决跨域存取Session问题

系列文章目录 Redis缓存穿透、击穿、雪崩问题及解决方法 Spring Cache的使用–快速上手篇 分页查询–Java项目实战篇 全局异常处理–Java实战项目篇 该系列文章持续更新&#xff0c;更多的文章请点击我的主页查看哦&#xff01; 文章目录 系列文章目录前言一、遇到的情况二、解…

golang 云效私有模块依赖拉取配置

相关文档 阿里官方文档 go 环境变量配置 export GOPRIVATEcodeup.aliyun.com 凭证设置 非常找重要,https密码配置克隆账户和克隆密码后续会用到 在 可以在 netrc 文件中指定凭据 Linux\MacOS 文件应该存放在执行账户的根目录下&#xff0c;即&#xff1a;~/.netrc sudo v…

SAS 9.3软件安装包下载及安装教程 办公软件

SAS 9.3软件简介&#xff1a; SAS 9.3是目前行业软件中的一款专业的数据统计分析软件&#xff0c;其核心功能包括高级分析、商业智能、客户智能、数据管理、风险管理和欺诈与安全智能六大模块&#xff0c;通过sas软件提供创新的分析、商业智能和数据管理软件与服务&#xff0c…

3Dsmax丨3dsmax2016软件下载安装教程 含全版本软件详细安装流程

大家好~随着计算机技术的发展&#xff0c;3D建模在各个行业中被广泛应用。以游戏行业为例&#xff0c;通过3D建模模型制作、灯光制作、材质渲染、细节润色、渲染合成等能够制作出游戏原神、王者荣耀、和平精英、英雄联盟、明日之后、光遇、哈利波特、剑网3、我的世界、第五人格…

SQL——关于bjpowernode.sql的33道经典例题之1-17

目录 1 查询每个部门最高薪水的人员名称 2 查询哪些人的薪水在部门平均薪水之上 3 查询每个部门的平均薪水等级 3.1 每个部门的平均薪水的等级 3.2 每个部门的平均的薪水等级 4 查询最高薪水&#xff08;不用max函数&#xff09; 5 查询平均薪水最高的部门的部门编号 …

如何使用 GPT-4 为博客目录页打造炫酷前端效果

前不久我用 cmd markdown 写了篇文章《项目 TO 的自我修养》&#xff0c;文章的目录如下&#xff1a; 当我把它发布到线上后&#xff0c;目录却只展示出了二级标题&#xff1a; 这哪行&#xff01;我猜这个可能就是加个配置啥的就能修复。于是马上就问 GPT-4 怎么办&#xff1f…

25.SSM-SpringMVC延续--拦截器与异常处理思想

目录 一、拦截器。 &#xff08;1&#xff09;拦截器的作用。 &#xff08;2&#xff09;拦截器与过滤器的区别。 &#xff08;3&#xff09;拦截器的方法。 &#xff08;4&#xff09;拦截器的快速入门。 &#xff08;5&#xff09;多拦截器。 &#xff08;5.1&#xf…

哈工大软件构造-设计模式笔记

文章目录 UML简单知识1&#xff0e;单例模式2.工厂方法模式3&#xff0e;迭代器模式4&#xff0e;策略模式5&#xff0e;建造者模式6&#xff0e;模板方法模式7&#xff0e;代理模式8&#xff0e;责任链模式9&#xff0e;抽象工厂模式10&#xff0e;适配器模式11&#xff0e;观…

计算机操作系统复习记录(总结 —— 快速入门和快速复习)

1. 操作系统 概念&#xff1a;是控制和管理整个计算机系统的硬件和软件资源&#xff0c;并合理地组织调度计算机的工作和资源的分配&#xff0c;以提供给用户和其它软件接口和环境&#xff0c;它是计算机系统中最基本的系统软件。 功能和目标&#xff1a; &#xff08;1&…

数据库同步 Elasticsearch 后数据不一致,怎么办?

1、实战线上问题 Q1&#xff1a;Logstash 同步 postgreSQL 到 Elasticsearch 数据不一致。 在使用 Logstash 从 pg 库中将一张表导入到 ES 中时&#xff0c;发现 ES 中的数据量和 PG 库中的这张表的数据量存在较大差距。如何快速比对哪些数据没有插入&#xff1f;导入过程中&am…

六:内存回收

内存回收&#xff1a; 应用程序通过 malloc 函数申请内存的时候&#xff0c;实际上申请的是虚拟内存&#xff0c;此时并不会分配物理内存。 当应用程序读写了这块虚拟内存&#xff0c;CPU 就会去访问这个虚拟内存&#xff0c; 这时会发现这个虚拟内存没有映射到物理内存&…

系统安全及应用

目录 一、账号安全控制 1&#xff09;系统账号清理 2&#xff09;密码安全控制 chage命令 示例 3&#xff09;命令历史限制 4&#xff09;终端自动注销 总结 账号安全 密码安全 二、系统引导和登录控制 1&#xff09;使用su命令切换用户 用途及用…

【学习笔记】字节数据和字节字符串(b“ “)那些事

文章目录 0 前言1 先来看看C语言中怎么处理这种字节数据1.1 使用总结 2 再来看看Python当中是怎么处理字节数据的 0 前言 最近在尝试用PyQt做一个上位机&#xff0c;遇到很多关于字节字符串的问题&#xff0c;这里简单总结几个关键点。 1 先来看看C语言中怎么处理这种字节数据…

RK3568平台开发系列讲解(Linux系统篇)共享内存的创建和映射过程

🚀返回专栏总目录 文章目录 一、共享内存的创建和映射过程流程梳理二、如何创建共享内存?三、如何将共享内存映射到虚拟地址空间?沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们一起学习下共享内存的创建和映射过程。 一、共享内存的创建和映射过程流程梳…

推荐5款重度电脑用户也未必知道的小众软件

作为一个重度电脑用户&#xff0c;你可能会经常使用一些软件来完成各种任务和娱乐。但是你知道有哪些好用的WIN10软件吗&#xff1f;今天我就为你介绍一下我推荐的五款WIN10软件&#xff0c;它们分别是&#xff1a; 1.反恶意软件——Malwarebytes Malwarebytes是一款专业的反…

AOP与SpringBoot使用AOP实例

AOP&#xff1a;Aspect Oriented Programming&#xff08;面向切面编程、面向方面编程&#xff09;&#xff0c;其实就是面向特定方法编程。 动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术&#xff0c;旨在管理bean对象的过程中&#xff0c;主要通过…

浅谈thrift协议+举例通用mockserver如何实现

目录 简单来说&#xff1a; 举个例子&#xff1a; 简单来说&#xff1a; 1&#xff09;是一个跨平台跨语言的通信协议&#xff0c;定义和创建跨语言服务&#xff0c;是一个高性能、轻量级RPC框架。 2&#xff09;开发者无需关注不同语言/相同语言服务间如何通信&#xff0c;…