Linux 中的管道:进程间数据传输的利器

server/2025/3/16 19:52:01/

个人主页:chian-ocean

文章专栏-Linux

前言

**进程间通信(Inter-Process Communication, IPC)**是指在操作系统中,不同进程之间交换数据或信息的方式。由于每个进程都有自己的地址空间,直接访问另一个进程的数据是不被允许的,因此需要使用IPC机制来实现进程间的通信

在这里插入图片描述

进程间通信

进程间通信的目的

进程间通信(IPC)的主要目的是让不同进程能够交换数据、协作工作和同步执行。由于每个进程有独立的内存空间,IPC提供了一种安全的方式让进程之间共享信息和资源。它的作用包括:

  • 数据共享:让多个进程能够共享数据。
  • 进程同步:协调进程的执行顺序,避免资源冲突。
  • 控制与协作:让进程之间进行协作或控制执行。
  • 高效并发:提高系统的处理能力和资源利用率。
  • 模块化设计:简化系统设计,通过独立的进程模块进行工作。

进程间通信的分类

Linux 提供了多种进程间通信机制,以适应不同的应用场景:

  • 管道、消息队列、共享内存:适用于同一系统内的进程间通信。
  • 信号量、套接字:用于进程同步与资源访问控制。
  • 信号和D-Bus:用于事件通知和消息传递。
  • 内存映射文件:用于高效的大数据交换。

管道

管道(Pipe)是一种常见的进程间通信(IPC)机制,用于不同进程之间传递数据。它本质上是一个数据缓冲区,允许一个进程将数据写入其中,另一个进程从管道中读取数据。管道是一种单向通信的方式,但可以通过创建多个管道来实现双向通信。

管道的类型:

  1. 匿名管道(Anonymous Pipe)
    • 匿名管道是无名的、临时的,通常用于父子进程之间的通信。
    • 由于匿名管道没有文件名,它们只能在创建它们的进程间使用。
    • 通过 pipe() 系统调用可以创建匿名管道,常用于同一系统内的进程间通信。
  2. 命名管道(Named Pipe,FIFO)
    • 命名管道是带有名字的管道,可以跨进程甚至跨系统使用。
    • 命名管道在文件系统中有一个路径,进程可以通过路径访问该管道。
    • 通过 mkfifo() 系统调用创建命名管道,适用于没有父子关系的进程间通信。

管道原理

写入数据
读取数据
父进程
管道
子进程

管道用于进程间的通信,特别是用于父子进程之间的简单数据传输。父进程创建管道,并通过管道的写端向管道写入数据;子进程从管道的读端读取数据。由于管道的特性,父子进程共享这对文件描述符,因此它们可以通过该管道进行通信。

  1. 父进程创建管道:父进程通过 pipe() 创建管道,得到一对文件描述符。
  2. 创建子进程:父进程调用 fork() 创建子进程,子进程会继承父进程的文件描述符。
  3. 父子进程通信:
    • 父进程通过写端写入数据到管道。
    • 子进程通过读端读取管道中的数据。
  4. 关闭管道:在数据传输完毕后,父子进程都应关闭管道的文件描述符,防止资源泄露。

匿名管道

匿名管道(Anonymous Pipe) 是一种用于父子进程间进行通信的机制,它允许数据在两个进程之间传输,而无需显式的文件名或路径。匿名管道的特点是它没有名字,并且通常只能用于具有亲缘关系的进程(即父进程与子进程)

匿名管道的特性

  1. 具有血缘关系的进程可以进行匿名管道通信。
  2. 管道只能进行单向通信。
  3. **父子进程会进行协同:**父进程和子进程之间的协同工作不仅可以在数据处理上进行配合,还可以在任务执行上互相协作。
  4. **管道是面向字节流的:**这意味着管道在传输数据时并不会关心数据的具体类型,而是以字节流的方式进行传输。
  5. 管道是基于文件,文件基于进程的生命周期。

匿名管道的SysCall

在这里插入图片描述

int pipe(int pipefd[2])

参数:

  • pipefd[2]:一个整数数组,长度为 2。pipefd[0] 用于读取数据(读端),pipefd[1] 用于写入数据(写端)。

返回值:

  • 成功:返回 0
  • 失败:返回 -1,并设置 errno 以指示错误原因。

功能:

pipe()创建一个管道并将两个文件描述符放入 pipefd 数组中:

  • pipefd[0] 是管道的读端,用于从管道中读取数据。
  • pipefd[1] 是管道的写端,用于向管道中写入数据。

这些文件描述符可以用于与其他进程或线程进行数据通信。

匿名管道示例

#include<iostream>
#include<string>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<cstdio>
#include<cassert>
#include<cstdlib>
#include<cstring>#define NUM 2  // 定义管道数组的大小
#define SIZE 1024  // 定义缓冲区大小// Reader函数:读取管道中的数据并打印出来
void Reader(int fd)
{char buffer[SIZE];  // 缓冲区用于存储读取的数据while (true){buffer[0] = 0;  // 清空缓冲区的第一个字节// 从管道读取数据ssize_t n = read(fd, buffer, sizeof(buffer));if (n > 0){buffer[n] = '\0';  // 添加字符串结束符std::cout << "father say:[" << "PID : " << getpid() << "]: " << buffer << std::endl;}// 可以通过 sleep(1) 暂停 1 秒,避免 CPU 过度使用(目前注释掉)//sleep(1);}
}// Writer函数:向管道中写数据
void Writer(int fd)
{char buffer[SIZE];  // 缓冲区用于存储要写入的数据std::string msg = "hello I am child";  // 子进程发送的消息int num = 1;  // 用于编号消息while (true){buffer[0] = 0;  // 清空缓冲区的第一个字节snprintf(buffer, sizeof(buffer), "child-PID: %d-%s-%d", getpid(), msg.c_str(), num);  // 格式化消息num++;  // 自增消息编号// 写入管道write(fd, buffer, strlen(buffer));sleep(1);  // 每次写入后暂停 1 秒}
}int main()
{int pipefd[NUM] = {0};  // 创建一个管道文件描述符数组// 创建管道int n = pipe(pipefd);if (n < 0) return -1;  // 如果创建管道失败,返回 -1// 创建子进程pid_t id = fork();if (id < 0) return -2;  // 如果创建子进程失败,返回 -2if (id == 0)  // 子进程部分{close(pipefd[0]);  // 关闭读端,因为子进程只需要写入数据// 调用 Writer 函数向管道中写数据Writer(pipefd[1]);exit(0);  // 子进程结束}// 父进程部分close(pipefd[1]);  // 关闭写端,因为父进程只需要读取数据// 调用 Reader 函数从管道中读取数据Reader(pipefd[0]);// 等待子进程结束pid_t ret = waitpid(id, NULL, 0);if (ret == 0){std::cout << "success return" << std::endl;  // 子进程正常退出}return 0;
}

代码流程:

  1. 创建管道
    • 使用 pipe() 创建一个管道,并通过 pipefd[0]pipefd[1] 分别访问读端和写端。
  2. 创建子进程
    • 使用 fork() 创建子进程。
    • 在子进程中,关闭管道的读端,调用 Writer() 函数向管道写数据。
    • 在父进程中,关闭管道的写端,调用 Reader() 函数从管道读取数据。
  3. 管道通信
    • 子进程通过写端 (pipefd[1]) 向管道写数据。
    • 父进程通过读端 (pipefd[0]) 从管道读取数据并打印。
  4. 进程同步
    • 使用 waitpid() 等待子进程的结束。

主要功能:

  • 父子进程的管道通信:父进程读取子进程写入管道的数据并输出。
  • 无限循环的读写操作:子进程不断向管道写入数据,而父进程不断从管道读取数据。

在这里插入图片描述

管道的4种情况(用代码自己验证)

  1. 读端正常,写端关闭:读端会挂起阻塞。
  2. 读端正常,写端写满:写端会阻塞。
  3. 读端正常,写端关闭:读端就会读到0(表示读到了尽头)
  4. 写端正常,读端关闭:OS会通过操作系统杀掉写端。(13号信号:管道信号);

在这里插入图片描述

命名管道

Linux 中的命名管道(Named Pipe,通常称为 FIFO)是一种特殊类型的文件,它用于在不同进程之间进行数据交换。它与匿名管道(Anonymous Pipe)相比,具有一些独特的特性。

命名管道的特性

  • 跨进程通信:命名管道不仅限于父子进程之间的通信,它可以用于任何两个在系统中运行的进程之间,甚至是属于不同用户的进程。
  • 阻塞行为:(这一点类似于匿名管道)
    1. 写操作阻塞:如果管道的缓冲区已满,写入数据的进程会被阻塞,直到有进程从管道中读取数据释放出空间。
    2. 读操作阻塞:如果管道为空,读取数据的进程会被阻塞,直到有进程写入数据。

命名管道的创建和销毁

创建

bash下
mkfifo name
  • p开头的是管道文件。

在这里插入图片描述

C++代码中
int mkfifo(const char *pathname, mode_t mode);
  • pathname:指定要创建的FIFO文件的路径。

  • mode:设置FIFO文件的权限,通常使用标准的文件权限值(例如 0664)。

在这里插入图片描述

销毁

bash
unlink name

在这里插入图片描述

C++代码下

int unlink(const char *pathname);
  • pathname:要删除的文件路径。

在这里插入图片描述

命名管道的示例

  • 提供server端进行读取管道。
  • 提供client端进行写入管道。

server

#include "piped.hpp"int main()
{// 创建命名管道(FIFO文件),进行进程间通信int n = mkfifo(FIFO_FILE, MODE);  // 使用 mkfifo 系统调用创建命名管道,FIFO_FILE 为管道文件路径,MODE 为权限模式if (n < 0) {perror("mkfifo");  // 如果创建管道失败,打印错误信息exit(FIFO_CREAT_ERR);  // 退出程序并返回自定义的错误码 FIFO_CREAT_ERR}// 打开命名管道,进行读取操作int fd = open(FIFO_FILE, O_RDONLY);  // 使用 open 系统调用打开管道文件进行只读操作if (fd < 0) {return FIFO_OPEN_ERR;  // 如果打开管道失败,返回自定义的错误码 FIFO_OPEN_ERR}// 不断读取管道中的数据while (true){char buffer[SIZE] = {0};  // 创建一个字符数组 buffer 来存放从管道读取的数据int n = read(fd, buffer, sizeof(buffer));  // 使用 read 系统调用从管道读取数据,读取的最大字节数为 buffer 的大小if (n == -1) {perror("read");  // 如果读取数据失败,打印错误信息exit(FIFO_READ_ERR);  // 退出程序并返回自定义的错误码 FIFO_READ_ERR}else if (n > 0) {buffer[n] = 0;  // 确保读取的数据是以 null 字符 '\0' 结束的,以方便处理为字符串std::cout << "PID :" << getpid() << " client said # " << buffer << std::endl;  // 打印读取的数据以及进程的 PID}else {break;  // 如果没有数据可读,退出循环}}close(fd);  // 关闭管道文件描述符return 0;  // 程序正常结束
}
代码解释
  1. 创建命名管道:
    使用 mkfifo() 创建一个命名管道,该管道的路径为 FIFO_FILE,其权限由 MODE 定义。如果创建失败,则通过 perror() 输出错误信息并退出。

  2. 打开命名管道:
    使用 open() 打开刚刚创建的命名管道文件,使用只读模式 (O_RDONLY)。如果管道打开失败,则退出并返回错误码。

  3. 读取数据:
    程序通过 read() 从管道中读取数据,每次读取的数据存放到 buffer 中,并且确保数据以 null 字符结束。读取到的数据会被打印出来,显示发送数据的客户端信息。

  4. 处理管道读取:
    如果读取操作成功且有数据,程序会打印客户端的消息。如果读取操作返回 0(表示管道关闭),则退出循环。

  5. 关闭文件描述符:
    当管道的数据读取完毕后,程序会通过 close() 关闭管道文件描述符。

cilent

#include "piped.hpp"  int main()
{// 打开命名管道文件(FIFO),以写模式打开int fd = open(FIFO_FILE, O_WRONLY);  // 使用 open 系统调用以写模式打开管道文件 FIFO_FILEif (fd == -1) {perror("open");  // 如果打开失败,打印错误信息exit(FIFO_OPEN_ERR);  // 退出程序并返回自定义的错误码 FIFO_OPEN_ERR}std::cout << "client open file done" << std::endl;  // 输出确认消息,表示管道文件已成功打开// 不断读取用户输入并将其写入管道while (true){std::string str;  // 定义一个字符串来存储用户输入std::cout << "Please Enter@";  // 提示用户输入getline(std::cin, str);  // 从标准输入读取一行数据并存储到 str 中std::cout << str << std::endl;  // 打印用户输入的内容// 将输入的字符串写入管道int n = write(fd, str.c_str(), str.size());  // 使用 write 系统调用将输入字符串写入管道if (n == -1) {perror("write");  // 如果写入失败,打印错误信息exit(FIFO_WRITE_ERR);  // 退出程序并返回自定义的错误码 FIFO_WRITE_ERR}}close(fd);  // 关闭管道文件描述符return 0;  // 程序正常结束
}
代码解释:
  1. 打开管道文件:
    • 使用 open() 函数以写模式(O_WRONLY)打开命名管道文件 FIFO_FILE。如果管道文件打开失败,程序会打印错误信息并退出。
  2. 用户输入并写入管道:
    • 程序通过 std::getline(std::cin, str) 获取用户的输入,并将其存储在 str 变量中。
    • 输入的字符串会被通过 write() 系统调用写入管道,写入的数据是通过 str.c_str() 获取的字符串指针,str.size() 获取的字符串大小。
  3. 错误处理:
    • 如果 write() 函数返回错误(即返回 -1),程序会打印出错误信息,并退出,返回错误码 FIFO_WRITE_ERR
  4. 关闭管道文件:
    • 在写入完成后,程序会通过 close() 关闭管道文件描述符。

头文件包含以及宏定义hpp

#pragma once#include <iostream>
#include <string>
#include <cstdlib>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>#define FIFO_FILE "./mypiped"
#define MODE 0664
#define SIZE 1024enum 
{FIFO_CREAT_ERR = 1 ,FIFO_OPEN_ERR      ,FIFO_READ_ERR      ,FIFO_WRITE_ERR
};

管道文件描述符。

头文件包含以及宏定义hpp

#pragma once#include <iostream>
#include <string>
#include <cstdlib>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>#define FIFO_FILE "./mypiped"
#define MODE 0664
#define SIZE 1024enum 
{FIFO_CREAT_ERR = 1 ,FIFO_OPEN_ERR      ,FIFO_READ_ERR      ,FIFO_WRITE_ERR
};

http://www.ppmy.cn/server/175508.html

相关文章

Word 小黑第17套

对应大猫18 在目录前面添加一个空白页 点布局 -分隔符 -下一页 制作封面图片移动不了 调整一下图片的环绕文字 对文档内容进行分节&#xff1a;在要分节内容的前面操作 布局 -分隔符 -下一页 &#xff08;通过导航窗格&#xff09; 修改定义新编号样式 将另一个文档中的样式…

基于SpringBoot的Mybatis和纯MyBatis项目搭建的区别

【由于之前学习MyBatis的时候是跟着视频敲的纯MyBatis项目&#xff0c;以至于在突然看到别人在SpringBoot项目里搭建MyBatis方式的时候很懵比…特此文字形式记录一下区别&#xff08;应该还有好多种其他方式是我不知道的&#xff0c;主要应该就是要知道关键的流程步骤&#xff…

翻硬币问题

小明正在玩一个“翻硬币”的游戏。桌上放着排成一排的若干硬币&#xff0c;用“”表示正面&#xff0c;用“o”表示反面&#xff08;是小写字母&#xff0c;不是零&#xff09;。比如可能情形是“**oo***ooo”&#xff0c;如果同时翻转左边的两个硬币&#xff0c;则变为“oooo*…

音视频入门基础:RTP专题(19)——FFmpeg源码中,获取RTP的音频信息的实现(下)

本文接着《音视频入门基础&#xff1a;RTP专题&#xff08;18&#xff09;——FFmpeg源码中&#xff0c;获取RTP的音频信息的实现&#xff08;上&#xff09;》&#xff0c;继续讲解FFmpeg获取SDP描述的RTP流的音频信息到底是从哪个地方获取的。本文的一级标题从“四”开始。 四…

钉钉项目报销与金蝶系统高效集成技术解析

钉钉报销【项目报销类】集成到金蝶付款单【画纤骨】的技术实现 在企业日常运营中&#xff0c;数据的高效流转和准确对接是提升业务效率的关键。本文将分享一个具体的系统对接集成案例&#xff1a;如何将钉钉平台上的项目报销数据无缝集成到金蝶云星空的付款单系统中。本次方案…

第四章-PHP文件包含

PHP文件包含 一&#xff0c;PHP文件包含简介 在 PHP 开发中&#xff0c;文件包含&#xff08;File Inclusion&#xff09;是一种代码复用和组织的重要机制&#xff0c;其核心目的是将代码模块化、提高可维护性。 文件包含的作用 1. 代码复用与模块化 拆分重复代码&#xff…

【后端】【django drf】Django DRF API 编写规范(程序设计规则)

Django DRF API 编写规范&#xff08;程序设计规则&#xff09; 为了确保 Django DRF 代码的可维护性、可扩展性和高质量&#xff0c;API 设计不仅要符合 RESTful 规范&#xff0c;还需要遵循一定的程序设计规则。以下是一些关键的编写规范&#xff0c;以保证代码的清晰性、可…

力扣 11.盛水最多的容器(双指针)

11. 盛最多水的容器 - 力扣&#xff08;LeetCode&#xff09; 代码区&#xff1a; class Solution { public:int maxArea(vector<int>& height) {//双指针int left 0,rightheight.size()-1;int maxarea0;while(left<right){maxareamax(maxarea,(right-left)*min(…