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

Linux编程:4、进程通信-管道(匿名管道)

一、管道的基本概念

  1. 定义
    管道是 Linux 中一种半双工(单向)的进程间通信方式,本质是内核中的一块缓冲区,类似特殊文件,数据遵循 “先进先出(FIFO)” 原则,读取后自动删除。
  2. 分类
    • 匿名管道:无文件名,仅用于有血缘关系的进程(如父子进程)通信。
    • 命名管道(FIFO):有文件名,可用于无血缘关系的进程通信
  3. 为什么使用管道:
    信号可以实现进程之间的通信,但是不能传递更多的数据。 解决方案:使用管道。

二、匿名管道的创建与使用

1. 创建管道 ——pipe系统调用

#include <unistd.h>
int pipe(int pipefd[2]);  // pipefd[0]为读端,pipefd[1]为写端
  • 成功返回 0,失败返回 - 1。
  • 管道缓冲区大小为PIPE_BUF(通常 4096 字节),写入超过该大小时会被分割。

2. 管道的特性

  • 单向通信:只能从读端读、写端写,若需双向通信需创建两个管道。
  • 阻塞机制
    • 读端:无数据时read阻塞,直至有数据写入。
    • 写端:缓冲区满时write阻塞,直至有数据被读取。
  • 文件描述符关闭影响
    • 写端全关闭时,读端read返回 0。
    • 读端全关闭时,写端write触发SIGPIPE信号(默认终止进程)。

三、管道在进程间的通信场景

1. 同一进程内的管道(无实际用途)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(void)
{int fd[2];int ret = pipe(fd);  // 创建一个匿名管道if (ret < 0) {perror("pipe failed.");exit(1);}char msg[] = "hello world";int  count = write(fd[1], msg, strlen(msg) + 1);printf("向管道写入 %d 个字节:%s\n", count, msg);// BUFSIZ 缓冲区默认大小为 BUFSIZ,具体大小与系统定义有关char buff[BUFSIZ];count = read(fd[0], buff, BUFSIZ);printf("从管道读到 %d 个字节: %s\n", count, buff);return 0;
}

2. 父子进程间的管道通信

  • 原理fork子进程复制父进程的文件描述符,从而父子进程可通过同一管道读写。
  • 实例代码
  • #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/wait.h>
    #include <unistd.h>
    int main(void)
    {int fd[2];int ret = pipe(fd);  // 创建一个匿名管道if (ret < 0) {perror("pipe failed.");exit(1);}// 创建一个进程// 此时管道的读端 fd[0]和写端 fd[1]被复制到子进程中ret = fork();if (ret == -1) {perror("fork failed.");exit(1);}if (ret == 0) {  // 在子进程中char buff[BUFSIZ];printf("子进程正在从管道读取数据...\n");// 如果父进程还没有向管道写入数据,此时读管道就会导致阻塞int count = read(fd[0], buff, BUFSIZ);printf("子进程从管道读到了%d 个字节:%s\n", count, buff);exit(EXIT_SUCCESS);}else {  // 在父进程中char msg[] = "hello world";sleep(3);int count = write(fd[1], msg, strlen(msg) + 1);printf("父进程向管道写入%d 个字节:%s\n", count, msg);int status;wait(&status);  // 等待任意一个子进程结束}return 0;
    }

3. 结合exec的管道通信

  • 场景:父进程创建管道后,子进程通过execl执行其他程序,利用管道传递数据。
  • 实现步骤
    1. 父进程创建管道并fork子进程。
    2. 子进程通过命令行参数接收管道读端文件描述符,或通过dup重定向标准输入 / 输出。
  • 代码示例(父进程向子进程传递数据)
    #include <cstdlib>
    #include <cstring>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/wait.h>int main()
    {// 创建一个管道int fd[2];int ret = pipe(fd);if (ret == -1) {perror("pipe failed.");exit(-1);}int pid = fork();if (pid < 0) {perror("fork failed.");exit(-2);}else if (pid == 0) {// 子进程读取数据char arg[8];sprintf(arg, "%d", fd[0]);printf("正在调用 test2进程...\n");execl("test2", "test2", arg, NULL);perror("execl failed");exit(EXIT_FAILURE);}else {// 父进程写入数据sleep(3);char msg[] = "hello world";int count = write(fd[1], msg, strlen(msg) + 1);printf("父进程向管道里写入了 %d 个字符: %s\n", count, msg);int status;wait(&status);}return 0;
    }
    #include <cstdlib>
    #include <cstring>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>int main(int argc, char* argv[])
    {// 检查参数数量是否正确if (argc != 2) {fprintf(stderr, "用法: %s <fd>\n", argv[0]);return EXIT_FAILURE;}// 从argv[1]获取文件描述符int fd;if (sscanf(argv[1], "%d", &fd) != 1) {fprintf(stderr, "错误: 无法将参数转换为整数\n");return EXIT_FAILURE;}char buff[BUFSIZ];int  count = read(fd, buff, BUFSIZ);if (count == -1) {perror("read failed");}else {printf("test2从管道里读取了 %d 个字符: %s\n", count, buff);}close(fd);  // 关闭读取端return 0;
    }

4. 管道重定向标准输入 / 输出

  • 原理:通过dupdup2将管道文件描述符复制到stdin(0)stdout(1),使程序直接通过标准流读写管道。
  • 代码示例(子进程从管道读标准输入)
    #include <cstdlib>
    #include <cstring>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/wait.h>
    #include <unistd.h>int main()
    {// 创建一个管道,用于父子进程间通信// fd[0] 是读端,fd[1] 是写端int fd[2];int ret = pipe(fd);if (ret == -1) {perror("pipe failed.");exit(-1);}// 创建子进程int pid = fork();if (pid < 0) {perror("fork failed.");exit(-2);}else if (pid == 0) {// 子进程代码// 子进程负责从管道读取数据并执行test4程序// 关闭标准输入,准备将管道读端重定向到标准输入close(0);   // 将管道的读端复制到文件描述符0(标准输入)dup(fd[0]); // 现在标准输入已经被替换为管道的读端// 关闭不需要的文件描述符,避免资源泄漏close(fd[0]); // 已经复制到标准输入,原fd不再需要close(fd[1]); // 子进程不使用写端,关闭它printf("正在调用 test4进程...\n");// 执行外部程序test4,该程序将从标准输入读取数据// 由于标准输入已被重定向,test4将从管道读取数据execl("test4", "test4", NULL);perror("execl failed"); // 如果execl失败,打印错误信息exit(EXIT_FAILURE);}else {// 父进程代码// 父进程负责向管道写入数据// 关闭标准输出,准备将管道写端重定向到标准输出close(1);// 将管道的写端复制到文件描述符1(标准输出)dup(fd[1]);// 现在标准输出已经被替换为管道的写端// 关闭不需要的文件描述符close(fd[0]); // 父进程不使用读端,关闭它close(fd[1]); // 已经复制到标准输出,原fd不再需要sleep(3);// 向标准输出写入数据,实际上数据会被写入管道char msg[] = "hello world";printf("%s\n", msg);// 刷新标准输出缓冲区,确保数据立即写入管道fflush(stdout);// 等待子进程结束,获取其退出状态int status;wait(&status);}return 0;
    }
    #include <cstdlib>
    #include <cstring>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>int main(int argc, char* argv[])
    {char buff[BUFSIZ];int  count = read(0, buff, BUFSIZ);if (count == -1) {perror("read failed");} else {printf("test4从标准输出里读取了 %d 个字符: %s\n", count, buff);}return 0;
    }


四、管道的应用场景

  1. Shell 命令管道:如ps -ef | grep test,前一命令输出通过管道作为后一命令输入。
  2. 父子进程间数据传输:适用于有血缘关系的进程间简单数据交换。
  3. 程序间解耦:通过管道将数据生产方与消费方分离,如日志系统。

五、注意事项

  1. 文件描述符关闭:未使用的读 / 写端需及时关闭,避免资源泄漏或阻塞。
  2. 缓冲区大小:单次写入不超过PIPE_BUF(4096 字节)时保证原子性,否则可能被分割。
  3. 信号处理:读端关闭时写管道会触发SIGPIPE,需通过signalsigaction处理以避免进程崩溃。
http://www.xdnf.cn/news/14449.html

相关文章:

  • 二手商城系统+SpringBoot + Vue (前后端分离)
  • 通用embedding模型和通用reranker模型,观测调研
  • 嵌入式学习笔记C语言阶段--17共用体和枚举
  • LG P4278 带插入区间K小值 Solution
  • SCADA|KingSCADA通过组合框选择修改变量的值
  • JS进阶 Day04
  • 2GT 环形闭口闭环同步带一种具有特定齿形和结构的传动带
  • MotleyCrew ——抛弃dify、coze,手动搭建多agent工作流
  • Cangejie Magic智谱AI文生图API实战详解
  • 洛谷 排队接水 贪心
  • 2025CVPR最佳论文系列
  • AI 产品设计头脑风暴
  • Leetcode 3583. Count Special Triplets
  • 【python深度学习】Day 54 Inception网络及其思考
  • 深入理解IOC与DI
  • PID 控制算法 | 参数整定 | 方法 / 仿真 / 应用案例
  • 图片压缩工具 | 按指定高度垂直切割图片
  • 归一化:深度学习的隐藏加速器,解密数据标准化的魔力
  • Spring 事务传播行为全景分析表
  • Java设计模式之创建型模式( 工厂方法模式)介绍与说明
  • 智能跃迁:企业大模型落地方法论与路径最佳实践
  • 逆向知识点
  • 5.5.2_2并查集的进一步优化
  • 运算符与优先级
  • Docker环境下的EFK日志分析实践:从Filebeat采集到Kibana可视化的完整部署指南
  • 【LeetCode 207】课程表(有向无环图 DAG、拓扑排序)
  • 在C++中进程间通信(IPC)
  • 数据库学习(七)——MySQL执行引擎
  • Google机器学习实践指南(非线性特征工程解析)
  • 人工智能学习37-Keras手写识别预测