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

UNIX下C语言编程与实践42-UNIX 无名管道:pipe 函数的使用与父子进程单向通信实现

一、无名管道的核心概念

在 UNIX 系统中,无名管道(Unnamed Pipe) 是最古老的进程间通信(IPC)工具之一,主要用于实现有血缘关系进程(如父子进程、兄弟进程)之间的单向数据传输。它具有以下核心特性:

  • 单向通信:管道是半双工(Half-duplex)的,数据只能从一端写入、另一端读取,无法双向同时传输。
  • 文件描述符关联:管道通过两个文件描述符实现操作——读端(fildes[0])和写端(fildes[1]),进程通过读写这两个描述符完成数据交换。
  • 血缘关系限制:无名管道由父进程创建,子进程通过 fork() 继承管道的文件描述符,因此仅能在有血缘关系的进程间使用,无法用于无关联进程(如两个独立启动的程序)。
  • 字节流特性:管道传输的数据是无结构的字节流,不保留消息边界,读取方需自行处理数据拆分(如约定固定长度或分隔符)。
  • 阻塞特性:当管道为空时,读操作会阻塞;当管道满时,写操作会阻塞,直到有数据被读取或空间被释放。

二、pipe 函数:创建无名管道的核心接口

pipe() 函数是 UNIX 系统中创建无名管道的核心系统调用,它会在进程的文件描述符表中分配两个关联的描述符,分别对应管道的读端和写端。

2.1 函数原型与参数

#include <unistd.h>// 成功返回 0,失败返回 -1 并设置 errno
int pipe(int fildes[2]);

参数说明:

  • fildes[2]:整型数组,用于存储管道的两个文件描述符:
    • fildes[0]:管道的读端,仅用于读取数据(如通过 read() 函数)。
    • fildes[1]:管道的写端,仅用于写入数据(如通过 write() 函数)。

2.2 函数使用步骤

  1. 创建管道:调用 pipe(fildes) 创建无名管道,若返回 -1 则表示创建失败(如系统管道数量达到上限)。
  2. 创建子进程:通过 fork() 创建子进程,子进程会继承父进程的文件描述符表,因此也拥有管道的读端和写端。
  3. 关闭无用端:根据通信方向(如父写子读),父进程关闭读端(close(fildes[0])),子进程关闭写端(close(fildes[1])),避免死锁或资源泄漏。
  4. 数据传输:父进程通过写端写入数据,子进程通过读端读取数据,完成单向通信。
  5. 关闭管道:通信结束后,关闭剩余的文件描述符(如父进程关闭写端,子进程关闭读端)。

三、父子进程单向通信实现:代码实例

下面通过一个完整的 C 语言实例(参考文档中的 pipe1.c),演示父进程向子进程发送数据、子进程读取并打印数据的单向通信流程。

3.1 完整代码实现

代码文件名:pipe_demo.c

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <assert.h>int main() {int fildes[2];  // 存储管道的读端和写端pid_t pid;      // 子进程 IDchar buf[256];  // 读取数据的缓冲区int read_len;   // 实际读取的字节数// 1. 创建无名管道assert(pipe(fildes) == 0);  // 断言确保管道创建成功,失败则终止程序printf("管道创建成功,读端 fd=%d,写端 fd=%d\n", fildes[0], fildes[1]);// 2. 创建子进程pid = fork();assert(pid != -1);  // 确保 fork 成功if (pid == 0) {// ------------------- 子进程逻辑 -------------------// 3. 子进程仅需读数据,关闭写端(避免资源泄漏和死锁)close(fildes[1]);printf("子进程(PID=%d):已关闭写端,准备读取数据...\n", getpid());// 4. 从管道读端读取数据(阻塞直到有数据)memset(buf, 0, sizeof(buf));  // 清空缓冲区read_len = read(fildes[0], buf, sizeof(buf));if (read_len > 0) {printf("子进程(PID=%d):读取到数据,长度=%d 字节\n", getpid(), read_len);printf("子进程(PID=%d):数据内容:%s\n", getpid(), buf);} else if (read_len == 0) {printf("子进程(PID=%d):管道写端已关闭,无更多数据\n", getpid());} else {perror("子进程 read 失败");  // 打印错误信息}// 5. 关闭读端,释放资源close(fildes[0]);printf("子进程(PID=%d):已关闭读端,退出\n", getpid());return 0;} else {// ------------------- 父进程逻辑 -------------------// 3. 父进程仅需写数据,关闭读端(避免资源泄漏和死锁)close(fildes[0]);printf("父进程(PID=%d):已关闭读端,准备写入数据...\n", getpid());// 4. 向管道写端写入数据const char *send_data = "Hello from Parent! This is a test message.";int write_len = write(fildes[1], send_data, strlen(send_data));if (write_len == strlen(send_data)) {printf("父进程(PID=%d):数据写入成功,长度=%d 字节\n", getpid(), write_len);} else {perror("父进程 write 失败");}// 5. 关闭写端(子进程会检测到 EOF)close(fildes[1]);printf("父进程(PID=%d):已关闭写端\n", getpid());// 6. 等待子进程退出,避免僵尸进程wait(NULL);printf("父进程(PID=%d):子进程已退出,通信完成\n", getpid());return 0;}
}

3.2 编译与运行

在 UNIX/Linux 环境下,通过以下命令编译并运行程序:

# 编译代码
gcc pipe_demo.c -o pipe_demo# 运行程序
./pipe_demo

3.3 预期运行结果

管道创建成功,读端 fd=3,写端 fd=4
父进程(PID=12345):已关闭读端,准备写入数据...
父进程(PID=12345):数据写入成功,长度=43 字节
父进程(PID=12345):已关闭写端
子进程(PID=12346):已关闭写端,准备读取数据...
子进程(PID=12346):读取到数据,长度=43 字节
子进程(PID=12346):数据内容:Hello from Parent! This is a test message.
子进程(PID=12346):已关闭读端,退出
父进程(PID=12345):子进程已退出,通信完成

四、无名管道的核心特性深入分析

理解无名管道的特性是正确使用它的关键,以下是对其核心特性的详细解析:

4.1 字节流与无消息边界

管道传输的数据是无结构的字节流,不保留消息的边界。例如,父进程分两次写入数据(如 "Hello!" 和 "World!"),子进程可能一次读取到所有数据("Hello!World!"),而非分两次读取。

解决方法:若需拆分消息,需在应用层约定规则,如:

  • 固定消息长度(如每次传输 10 字节);
  • 使用分隔符(如 \n 或 \0)标记消息结束;
  • 先传输消息长度(如 4 字节整数),再传输消息内容。

4.2 半双工与单向通信

管道是半双工的,同一时间只能单向传输数据。若需双向通信,需创建两个管道(如管道 1 用于父写子读,管道 2 用于子写父读)。

4.3 阻塞特性

管道的读写操作默认是阻塞的,具体表现为:

操作触发条件阻塞行为
读操作(read)管道为空,且写端未关闭阻塞,直到有数据写入或写端关闭(此时 read 返回 0)
写操作(write)管道已满,且读端未关闭阻塞,直到有数据被读取(释放空间)或读端关闭(此时 write 失败,返回 -1 并设置 errno=EPIPE)

注意:若读端已关闭,写端继续写入会触发 SIGPIPE 信号,默认导致进程终止。可通过 signal(SIGPIPE, SIG_IGN) 忽略该信号,避免进程意外退出。

4.4 缓冲区大小

UNIX 管道有默认的缓冲区大小(通常为 4KB 或 8KB,取决于系统),当写入数据超过缓冲区大小时,写操作会阻塞,直到部分数据被读取。

查看与修改管道缓冲区大小的方法:

  • 查看默认大小:通过 fcntl() 函数获取:
    #include <fcntl.h>int buf_size = fcntl(fildes[1], F_GETPIPE_SZ);
    printf("管道默认缓冲区大小:%d 字节\n", buf_size);
    
  • 修改缓冲区大小:通过 fcntl() 函数设置(需注意系统限制,不能超过 PIPE_MAX,通常为 65536 字节):
    int new_size = 16384;  // 16KB
    int ret = fcntl(fildes[1], F_SETPIPE_SZ, new_size);
    if (ret == new_size) {printf("管道缓冲区大小已设置为 %d 字节\n", new_size);
    } else {perror("设置管道缓冲区大小失败");
    }
    

五、使用无名管道的注意事项与常见错误

5.1 关键注意事项

  • 关闭无用的文件描述符:父进程创建管道后,必须关闭读端(若仅写),子进程必须关闭写端(若仅读)。否则会导致:
    • 子进程读操作阻塞(因为父进程未关闭读端,管道始终有“潜在写者”,read 不会返回 0);
    • 系统资源泄漏(未关闭的文件描述符会一直占用,直到进程退出)。
  • 避免管道破裂(Broken Pipe):若读端已关闭,写端继续写入会触发 SIGPIPE 信号,需提前忽略该信号或检测写操作返回值。
  • 处理僵尸进程:父进程需通过 wait() 或 waitpid() 等待子进程退出,避免子进程成为僵尸进程。
  • 数据读取完整性read() 函数可能返回部分数据(即使管道中有更多数据),需循环读取直到获取所有需要的数据。

5.2 常见错误与解决方法

错误现象

可能原因

解决方法

pipe() 返回 -1,errno=EMFILE

进程已打开的文件描述符数量达到上限

1. 关闭无用的文件描述符;2. 通过 ulimit -n 查看/修改进程最大文件描述符数

子进程读操作一直阻塞

父进程未关闭读端,管道始终有“潜在写者”

父进程在写入数据后,立即关闭写端(close(fildes[1])

写操作返回 -1,errno=EPIPE

读端已关闭,管道破裂

1. 确保读端在写操作期间未关闭;2. 忽略 SIGPIPE 信号(signal(SIGPIPE, SIG_IGN)

读取的数据不完整

read() 可能返回部分数据,需循环读取

使用循环读取,直到获取所有数据或 read 返回 0(写端关闭):
int total_read = 0;
while (total_read < expected_len) {int ret = read(fildes[0], buf + total_read, expected_len - total_read);if (ret <= 0) break;total_read += ret;
}

无名管道是 UNIX 系统中实现父子进程单向通信的轻量级工具,核心依赖 pipe() 函数创建的两个文件描述符(读端和写端)。其优势在于实现简单、开销小,适合小规模的父子进程数据传输;但也存在局限性,如仅支持血缘关系进程、半双工通信、无消息边界等。

在实际开发中,需注意关闭无用的文件描述符、处理阻塞特性和数据完整性,并根据需求选择合适的通信方式(如双向通信需创建两个管道,无血缘关系进程需使用有名管道 FIFO 或其他 IPC 工具)。

http://www.dtcms.com/a/450155.html

相关文章:

  • golang编译
  • Go语言入门(21)-错误处理
  • 实验二:链表
  • 在线免费开网站企业网站管理系统设计报告
  • 什么网站做ppt模板wordpress网页标签图标
  • 专栏丨华为HN8145XR光猫获取超级管理员密码
  • 小型企业网站开发公司wordpress 中文开发
  • Redis rdb持久化
  • AgentWorkflow 实战:从单 Agent 到多 Agent 协作的完整方案
  • 数据懒加载和虚拟列表
  • 江苏省建设注册中心网站首页在线制作简历网站
  • Java “线程池(2)”面试清单(含超通俗生活案例与深度理解)
  • Linux内核kallsyms符号压缩与解压机制
  • 米思米网站订单取消怎么做基金会网站模板
  • 公司网站源码做智能家居网站需要的参考文献
  • 11. Pandas 数据分类与区间分组(cut 与 qcut)
  • 找家里做的工作到什么网站淄博五厘网络技术有限公司
  • 国外哪些网站做产品推广比较好四川建设人员信息查询
  • 第二章:软件需求
  • AI Agent赋能产品经理:从需求分析到用户增长的全流程实践
  • 网站服务公司案例广州网站建设优化公司
  • AI学习日记——神经网络参数的更新
  • Java进阶教程,全面剖析Java多线程编程,多线程和堆内存栈内存的关系,笔记20
  • 建设春风摩托车官方网站百度站长论坛
  • 长春企业网站建设公司建设银行广州招聘网站
  • 网站 开发 周期定制app开发软件
  • 怎么做网站 ppt货代网站制作
  • 2025-10-06 Python不基础12——class原理
  • 龙泉驿建设局网站谷歌seo是什么职业
  • 从东方仙盟筑基期看 JavaScript 动态生成图片技术-东方仙盟