Unix进程间的通信方式——无名管道pipe-学习笔记
一、何为进程间通信
进程间通信(IPC,InterProcess Communication),是指在不同进程之间传播或交换信息。简单说就是进程之间可以相互发送数据。
IPC的方式通常有无名管道(PIPE)、有名管道(FIFO)、信号(Signal)、共享内存(Share Memory)、消息队列(Message Queue)、信号灯集(Semaphore Set)、套接字(Socket)、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。Socket用在网络编程中。
二、无名管道
管道通常指无名管道,是 UNIX 系统IPC最古老的形式。
特点有:
1、它是半双工的(即数据只能在一个方向上流动,单向),具有固定的读端和写端。
2、它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间),实现依赖父子进程文件共享。
3、它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
4、读pipe时,写端存在,有数据则读,无则阻塞;读pipe时,写端不存在,不能有数据,读则立刻返回。
5、写pipe时,读端存在,有空间则成功写,无空间则阻塞;写pipe时,读端不存在,有空间则管道破裂,无空间不存在。
6、写管道上限是65536
函数原型:
1 #include <unistd.h>
2 int pipe(int pipefd[2]);
由参数pipefd[2]返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。
单个进程中的管道几乎没有任何用处。通常,调用pipe的进程接着调用fork,这样就创建了从父进程到子进程(或反向)的IPC通道。
父子进程都有读端和写端,子进程的是从父进程复制过来的。
进程复制的时候复制了PCB、文件结构体,不止拷贝了文件描述符。
观察单进程半双工管道的两种办法
调用fork之后的半双工管道
调用fork之后做什么取决于我们想要有的数据流的方向。例如,对于从父进程到子进程的管道,父进程关闭管道的读端(fd[0]),子进程则关闭写端(fd[1])。
从父进程到子进程管道
举例:
1 /*例1:只有一个进程,读写管道文件*/ 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <assert.h> 5 #include <string.h> 6 #include <unistd.h> 7 8 int main() 9 { 10 int fd[2]; 11 pipe(fd); 12 13 write(fd[1],"hello world",12); 14 15 sleep(2); 16 17 char buff[128]; 18 int n = read(fd[0],buff,127); 19 printf("read:%s\n",buff); 20 21 close(fd[0]); 22 close(fd[1]); 23 }
1 /*例2:创建由父进程到子进程的管道,父进程写入helloworld,子进程读取数据到buf,然后打印输出。*/
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <assert.h>
6 #include <unistd.h>
7
8 int main()
9 {
10 int fd[2];
11 pipe(fd);//fd[0]是读,fd[1]是写
12 if(fork() != 0)
13 {
14 close(fd[0]);
15 write(fd[1],"helloworld",10);
16 }
17 else
18 {
19 close(fd[1]);
20 char buf[128] = {0};
21 read(fd[0],buf,127);
22 printf("%s\n",buf);
23 }
24 return 0;
25 }
1 /*例3:使用管道文件,创建由父进程到子进程的管道,父进程循环输入数据,子进程循环读取数据,当写端输入end时,父子线程都结束。*/
2 #include <stdio.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <stdlib.h>
6 #include <assert.h>
7
8 int main()
9 {
10 int fd[2];
11 pipe(fd);
12
13 pid_t pid = fork();
14
15 if(pid == 0)
16 {
17 close(fd[1]);
18 char buff[128] = {0};
19 int n = 0;
20 while((n = read(fd[0],buff,127)) > 0)
21 {
22 printf("child read:%s\n",buff);
23 }
24 close(fd[0]);
25 }
26 else
27 {
28 close(fd[0]);
29 while(1)
30 {
31 char buff[128] = {0};
32 printf("input:\n");
33 fgets(buff,128,stdin);
34 if(strncmp(buff,"end",3)==0)
35 {
36 break;
37 }
38 write(fd[1],buff,127);
39 }
40 close(fd[1]);
41 }
42 exit(0);
43 }
注意:
(1)当读一个写端已被关闭的管道是,在所有数据都被读取后,read返回0,以指示达到文件结束处。管道的写端彻底关闭(父子进程的写端都得关闭,否则会有进程处于未关闭状态,还在等待写),读端返回一个0,父子进程都得关闭。
(2)如果写一个读端已被关闭的管道,则产生信号SIGPIPE。如果忽略该信号或者捕捉该信号并从处理程序返回,则write返回-1,errno设置为EPIPE。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!