1、概述
无名管道用于有亲缘关系的进程间的通信,管道字如其名,它就像在两个进程之间铺设了一条管道,进程通过管道进行数据交互。无名管道是没有名字的,它由pipe或者pipe2函数创建,与之对应的是有名管道,有名管道在下一节介绍。
以pipe函数为例,其函数原型为int pipe(int pipefd[2]); pipefd[2]是输出变量,需要传入具有两个成员的整形数组,函数调用成功后会由系统填入值,其中pipefd[0]用作读,pipefd[1]用作写,读写函数用read和write。
pipe函数介绍如下:
函数原型 | int pipe(int pipefd[2]); |
头文件 | unistd.h |
功能 | 创建无名管道 |
参数 | [out] pipefd[2]:含有两个成员的整形数组,pipe调用成功后pipefd[0]用于读数据,pipefd[1]用于写数据 |
返回 | 成功返回0,失败返回-1 |
pipe2函数介绍如下:
函数原型 | int pipe2(int pipefd[2], int flags); |
头文件 | unistd.h |
功能 | 创建无名管道,并可以设置属性 |
参数 |
[out] pipefd[2]:同pipe [in] flags:可以传入两个标志 #O_NONBLOCK:非阻塞属性,在读数据时即使管道内没有数据,read函数也会立即返回,返回值为-1。 #O_CLOEXEC:关闭子进程无用描述符,可参见:https://blog.csdn.net/chrisniu1984/article/details/7050663 如果传入0,那么效果与pipe一样。 |
返回 | 成功返回0,失败返回-1 |
2、测试程序
先写一个正常使用的程序:
1 /** 2 * filename: pipe.c 3 * author: Suzkfly 4 * date: 2021-02-09 5 * platform: Ubuntu 6 * 执行程序,父进程在终端中输入字符,按回车键将数据放入管道,子进程能将管道数据 7 * 读出。MODE为1时读阻塞,MODE为2及MODE为3是通过两种方法实现读不阻塞。 8 */ 9 #include <unistd.h> 10 #include <stdio.h> 11 #include <string.h> 12 #include <fcntl.h> 13 14 #define MODE 1 15 16 int main(int argc, const char *argv[]) 17 { 18 int pipefd[2]; 19 int ret = 0; 20 int pid = 0; 21 char buf[128] = { 0 }; 22 int attr = 0; 23 24 #if (MODE == 1) 25 ret = pipe(pipefd); /* 创建管道(默认阻塞) */ 26 #elif (MODE == 2) 27 ret = pipe2(pipefd, O_NONBLOCK); /* 创建非阻塞管道 */ 28 #elif (MODE == 3) 29 ret = pipe(pipefd); /* 创建管道,并通过fcntl设置为非阻塞 */ 30 attr = fcntl(pipefd[0], F_GETFL); 31 attr |= O_NONBLOCK; 32 fcntl(pipefd[0], F_SETFL, attr); 33 #endif 34 if (ret == -1) { 35 printf("pipe failed\n"); 36 return 0; 37 } 38 39 printf("MODE = %d\n", MODE); 40 41 pid = fork(); 42 if (pid > 0) { /* 父进程 */ 43 while (1) { 44 memset(buf, 0, sizeof(buf)); 45 scanf("%s", buf); 46 ret = write(pipefd[1], buf, strlen(buf)); 47 printf("parent write ret = %d\n", ret); 48 } 49 } else if (pid == 0) { /* 子进程 */ 50 while (1) { 51 memset(buf, 0, sizeof(buf)); 52 ret = read(pipefd[0], buf, sizeof(buf)); 53 printf("child read ret = %d\n", ret); 54 printf("child receive:%s\n", buf); 55 56 if ((MODE == 2) || (MODE == 3)) { 57 sleep(1); 58 } 59 } 60 } 61 62 return 0; 63 }
测试结果:
3、问题解答
1. 用pipe创建出来的管道是单向的,是不是指的是只能从一个进程发送数据给另一个进程?
答:不是,单向指的是文件描述符是单向的,pipefd[1]只能写数据,pipefd[0]只能读数据,在调用fork之后实际上有两套文件描述符(但是只有1个管道),任何一个进程向pipefd[1]写数据之后,任何一个进程都能从pipefd[0]中读数据出来(可以自己写自己读)。
2. 如何实现进程之间数据单向性?
答:在要写数据的进程中关闭pipefd[0],在要读数据的进程中关闭pipefd[1]。
3. 如果所有的读文件描述符都被关闭了,此时仍然向管道内写数据会发生什么?
答:写数据的进程会收到SIGPIPE(管道破裂)信号,默认情况下写数据的进程会退出,也可以通过程序捕捉该信号,自行处理该信号。
4. 如果所有的写文件描述符都被关闭了,此时仍然向管道读数据会发生什么?
答:如果管道内有数据,则可以将数据读出来,如果管道内没数据,那么read也会立即返回,返回值为0。
5. 如果一直向管道写数据,但不读,会发生什么?
经过测试,发现当下一次写操作将要超过65536字节时,写会阻塞。简单理解就是如果管道内有65536字节时,写入任意数量的字节都将阻塞,如果管道内有63K字节,那么一次性写入1024个字节不会阻塞,但如果一次性写入1025个字节则会阻塞。这65536字节就是管道容量。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?