news 2026/3/19 9:05:58

应用——管道与文件描述符

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
应用——管道与文件描述符

一、管道(Pipe)

1. 基本概念

  • 管道是一种半双工的通信方式,数据只能单向流动。

  • 只能在具有亲缘关系的进程之间使用。

  • 管道本质上是一个内核缓冲区,通过文件描述符进行读写操作。

  • 包括读端fd[0]和写端fd[1]

2. 创建管道

int fd[2]; pipe(fd);

3. 读阻塞(Read Blocking)

  • 如果管道为空,读操作会阻塞,直到有数据写入。

示例代码:

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main(int argc, char **argv) { int fd[2]={0}; int ret = pipe(fd); if(-1 == ret) { perror("pipe error\n"); return 1; } pid_t pid = fork(); if(pid>0) { close(fd[0]); int i = 3; while(i--) { printf("father 准备数据\n"); sleep(1); } char buf[1024]="hello ,son"; write(fd[1],buf,strlen(buf)); close(fd[1]); } else if(0== pid) { close(fd[1]); char buf[1024]={0}; // 读阻塞 示例 read(fd[0],buf,sizeof(buf)); printf("father say:%s\n",buf); close(fd[0]); } else { perror("fork"); return 1; } return 0; }
  • 子进程调用read时,父进程尚未写入数据,子进程阻塞等待。

  • 父进程睡眠3秒后写入数据,子进程才继续执行。

4. 写阻塞(Write Blocking)

  • 如果管道缓冲区满,写操作会阻塞,直到有空间写入。

示例代码:

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main(int argc, char **argv) { int fd[2]={0}; int ret = pipe(fd); if(-1 == ret) { perror("pipe error\n"); return 1; } pid_t pid = fork(); if(pid>0) { close(fd[0]); char buf[1024]={0}; memset(buf,'a',sizeof(buf)); int i = 0 ; // 写阻塞 示例 for(i=0;i<65;i++) { write(fd[1],buf,sizeof(buf)); printf("%d\n",i); } close(fd[1]); } else if(0== pid) { close(fd[1]); while(1) { sleep(1); } close(fd[0]); } else { perror("fork"); return 1; } return 0; }
  • 父进程循环写入数据,直到管道满,后续写入会阻塞。

  • 子进程不读数据,导致写端持续阻塞。

5. 管道破裂(Broken Pipe)

  • 当读端关闭,写端继续写入时,会触发SIGPIPE信号,默认行为是终止进程。

示例代码:

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(int argc, char **argv) { int fd[2] = {0}; int ret = pipe(fd); if (-1 == ret) { perror("pipe error\n"); return 1; } pid_t pid = fork(); if (pid > 0) { close(fd[0]); sleep(3); char buf[1024] = "hello ,son"; //管道破裂 gdb 查看 //Program received signal SIGPIPE, Broken pipe write(fd[1], buf, strlen(buf)); printf("--------------\n"); close(fd[1]); } else if (0 == pid) { close(fd[1]); close(fd[0]); exit(1); } else { perror("fork"); return 1; } return 0; }
  • 子进程关闭了读端,父进程写入数据时触发管道破裂。

6. 读取管道结束(EOF)

  • 当写端关闭,读端读取完所有数据后,read返回0,表示管道结束。

示例代码:

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(int argc, char **argv) { int fd[2] = {0}; int ret = pipe(fd); if (-1 == ret) { perror("pipe error\n"); return 1; } pid_t pid = fork(); if (pid > 0) { close(fd[0]); char buf[1024] = "hello ,son"; write(fd[1], buf, strlen(buf)); close(fd[1]); exit(0); } else if (0 == pid) { close(fd[1]); sleep(3); while (1) { char buf[1024] = {0}; int ret = read(fd[0], buf, sizeof(buf)); // ret ==0 enf of pipe if(ret<=0) { break; } printf("father say:%s\n", buf); } close(fd[0]); } else { perror("fork"); return 1; } return 0; }
  • 父进程写入数据后关闭写端。

  • 子进程读取数据,当read返回0时,退出循环。

7. 管道复制文件

  • 管道可用于父子进程间传递大量数据,如文件复制。

示例代码:

#include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(int argc, char **argv) { int fd[2] = {0}; int ret = pipe(fd); if (-1 == ret) { perror("pipe error\n"); return 1; } pid_t pid = fork(); if (pid > 0) { close(fd[0]); int src_fd = open("/home/linux/1.png", O_RDONLY); if (-1 == src_fd) { perror("open src"); return 1; } while (1) { char buf[1024] = {0}; int rd_ret = read(src_fd, buf, sizeof(buf)); if(rd_ret<=0) { break; } write(fd[1], buf, rd_ret); } close(fd[1]); close(src_fd); } else if (0 == pid) { close(fd[1]); int dst_fd = open("2.png",O_WRONLY|O_CREAT|O_TRUNC,0666); if (-1 == dst_fd) { perror("open dst"); return 1; } while(1) { char buf[1024]={0}; int rd_ret = read(fd[0],buf,sizeof(buf)); if(rd_ret<=0) { break; } write(dst_fd,buf,rd_ret); } close(fd[0]); close(dst_fd); } else { perror("fork"); return 1; } return 0; }
  • 父进程读取文件内容,通过管道传递给子进程。

  • 子进程从管道读取数据并写入目标文件。


二、命名管道(FIFO)

1. 基本概念

  • FIFO 是一种命名管道,存在于文件系统中。

  • 可用于无亲缘关系的进程间通信。

  • 使用mkfifo创建 FIFO 文件。

2. 创建 FIFO

mkfifo("myfifo", 0666);

3. 读写阻塞特性

  • 读端先打开:会阻塞,直到写端打开。

  • 写端先打开:会阻塞,直到读端打开。

示例代码:

  • 写端程序

    #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include <errno.h> int main(int argc, char **argv) { int ret = mkfifo("myfifo", 0666); if (-1 == ret) { // 如果是文件已存在的错误,程序就继续运行 if (EEXIST == errno) { } else //如果是其他的错误,进程结束 { perror("mkfifo"); return 1; } } // open 会阻塞, //写段先运行 ,写段会等读段出现, // 读段先运行 ,读段会等写段出现, int fd = open("myfifo", O_WRONLY); if (-1 == fd) { perror("open"); return 1; } char buf[] = "hello,friend...\n"; write(fd, buf, strlen(buf) + 1); close(fd); return 0; }
  • 读端程序

    #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> int main(int argc, char **argv) { int ret = mkfifo("myfifo", 0666); if (-1 == ret) { // 如果是文件已存在的错误,程序就继续运行 if (EEXIST == errno) { } else //如果是其他的错误,进程结束 { perror("mkfifo"); return 1; } } // open 会阻塞, //写段先运行 ,写段会等读段出现, // 读段先运行 ,读段会等写段出现, int fd = open("myfifo", O_RDONLY); if (-1 == fd) { perror("open"); return 1; } char buf[1024] = {0}; read(fd, buf, sizeof(buf)); printf("fifo_w:%s\n", buf); close(fd); // remove("myfifo"); return 0; }

4. 错误处理

  • 如果 FIFO 已存在,mkfifo会失败,errnoEEXIST,通常忽略该错误。


三、文件描述符与 FILE* 转换

1.fileno:从 FILE* 获取文件描述符

FILE *fp = fopen("/etc/passwd", "r"); int fd = fileno(fp);

示例代码:

#include <stdio.h> #include <unistd.h> #include <fcntl.h> int main(int argc, char **argv) { FILE* fp = fopen("/etc/passwd","r"); if(NULL== fp) { perror("fopen"); return 1; } //fgets/fputs // FILE* -> int int fd = fileno(fp); char buf[100]={0}; read(fd,buf,sizeof(buf)-1); printf("%s",buf); fclose(fp); return 0; }
  • 使用fopen打开文件得到FILE*

  • 使用fileno获取对应的文件描述符,用于read等系统调用。

2.fdopen:从文件描述符获取 FILE*

int fd = open("/etc/passwd", O_RDONLY); FILE *fp = fdopen(fd, "r");

示例代码:

#include <stdio.h> #include <unistd.h> #include <fcntl.h> int main(int argc, char **argv) { int fd = open("/etc/passwd",O_RDONLY); if(-1== fd) { perror("open"); return 1; } //read /write char buf[100]={0}; FILE* fp = fdopen(fd,"r") ; if(NULL == fp) { perror("fdopen"); return 1; } fgets(buf,sizeof(buf),fp); printf("buf :%s",buf); fclose(fp); return 0; }
  • 使用open打开文件得到文件描述符。

  • 使用fdopen转换为FILE*,用于fgets等标准 I/O 函数。


四、总结

通信方式特点适用场景
无名管道半双工、亲缘进程、内存缓冲区父子进程间通信
命名管道有名字、文件系统可见无亲缘关系进程间通信
文件描述符系统调用、低级 I/O直接操作文件或设备
FILE*标准 I/O、带缓冲区高级文本或流式操作

注意事项:

  • 管道通信时,及时关闭不需要的文件描述符。

  • 注意处理 SIGPIPE 信号,避免进程意外终止。

  • FIFO 使用时注意读写端的阻塞与同步。

  • 文件描述符与 FILE* 可以相互转换,便于混合使用系统调用和标准 I/O。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/13 5:52:38

pgconf_asia_2017_logical_replication_us_20171204-1

Logical Replication Internals Agenda What is Logical Replication?Let’s try!ArchitectureRestrictionsTrouble shooting What is Logical Replication? What is Logical Replication? Is PostgreSQL 10 new featuresReplicate per tableReplicate per transaction…

作者头像 李华
网站建设 2026/3/15 23:39:38

leetcode 762. 二进制表示中质数个计算置位

Problem: 762. 二进制表示中质数个计算置位 解题过程 log2计算二进制长度&#xff0c;然后统计1个数&#xff0c;查看集合是否是素数&#xff0c;计算是否是素数&#xff0c;若是则放入集合 Code class Solution { public:int countPrimeSetBits(int left, int right) {int le…

作者头像 李华
网站建设 2026/3/4 0:30:16

为啥yyyy-MM-dd HH:mm:ss的MM和HH设计为大写

yyyy-MM-dd HH:mm:ss 中的大写 MM 和 HH 是 Java 日期格式化中的约定&#xff0c;原因如下&#xff1a; 1. 区分不同的时间单位&#xff08;主要目的&#xff09; 月份 (Month) vs 分钟 (Minute) // 大写的 M 表示月份 (Month) // 小写的 m 表示分钟 (minute)SimpleDateForm…

作者头像 李华
网站建设 2026/3/17 10:30:47

基于java的SpringBoot/SSM+Vue+uniapp的身体健康管理系统的详细设计和实现(源码+lw+部署文档+讲解等)

文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言 &#x1f31e;博主介绍&#xff1a;✌全网粉丝15W,CSDN特邀作者、211毕业、高级全…

作者头像 李华
网站建设 2026/3/4 3:21:52

走gateway接口偶现返回Internal Server Error

1.现象&#xff1a;业务反馈接口返回Internal Server Error经排查sringcloudgateway中有这个日志报错&#xff1a;18:49:40.317 [reactor-http-epoll-4] ERROR org.springframework.core.log.CompositeLog.error(CompositeLog.java:102) [traceId: ] - [009a73dc-74671082] 500…

作者头像 李华
网站建设 2026/3/14 13:54:44

35 岁以后的运维工程师该何去何从?出路究竟在何方?

运维工程师的出路在哪里&#xff0c;尤其是 35 岁以后&#xff1f; 最近在某乎看到个问题&#xff0c;“运维的出路在哪里&#xff0c;特别是35以后?”&#xff0c; 网友 1&#xff1a;孩子快跑 打好基础&#xff0c;网络&#xff0c;安全&#xff0c;数据库&#xff0c;服务…

作者头像 李华