什么是管道?—–我们把从一个进程连接到另一个进程的一个数据流成为管道。
作用:使进程间相互通信。
管道分为两种:1、匿名管道;2、命名管道
匿名管道
创建一个匿名管道——函数原型:int pipe(int fd[2]);
参数:fd是文件描述符组,fd[0]表示读端,fd[1]表示写端。(0像读东西的嘴,1像写字的笔)。
返回值:创建成功返回0;失败返回错误代码。
写一个测试用例:
1#include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<unistd.h>
5 //从键盘读取数据额,写入数据到管道,从管道读取数据,写到屏幕
6 int main()
7 {
8 int fds[2];
9 int p = pipe(fds);
10 if(p<0){
11 perror("pipe failded");
12 exit(1);
13 }
14 printf("pipe ok\n");
15 //从标准输入读数据
16 while(1){
17 char buff[1024] = {0};
18 ssize_t read_size = read(0,buff,sizeof(buff)-1);
19 if(read_size<0){
20 perror("read failed");
21 return 1;
22 }
23 if(read_size==0){
24 //读到EOF
25 printf("read done\n");
26 return 0;
27 }
28 buff[read_size] = '\0';
29 //写入管道
30 write(fds[1],buff,strlen(buff));
31 //从管道读数据
32 char out_buff[1024] ={0};
33 ssize_t reads = read(fds[0],out_buff,sizeof(out_buff)-1);
34 if(reads<0)
35 {
36 perror("read pipe failded");
37 return 1;
38 }
39 out_buff[reads] = '\0';
40 //写到标准输出上
41 write(1,out_buff,strlen(out_buff));
42 }
43 return 0;
44 }
结果:
我们在实现一个父子进程间利用管道通信的例子:
fork之前父子进程都可从管道读和写;fork之后子进程只能写(或者读),父进程只能读(或者写)。
代码:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<string.h>
5
6 int main()
7 {
8 int fds[2];
9 int ret = pipe(fds);
10 if(ret<0){
11 perror("pipe");
12 return 1;
13 }
14 pid_t pid = fork();
15 if(pid>0){
16 //father
17 //从管道中读
18 char buff[20] = {0};
19 close(fds[1]);//父进程关闭写端
20 ssize_t read_size = read(fds[0],buff,sizeof(buff)-1);
21 if(read_size<0){
22 perror("read faild");
23 return 1;
24 }
25 if(read_size==0){
26 printf("read done\n");
27 return 0;
28 }
29 buff[read_size] = '\0';
30 //写到标准输出
31 printf("%s\n",buff);
32 }else if(pid==0){
33 //child
34 i close(fds[0]);//子进程关闭i
35 write(fds[1],"hello word",10);
36 close(fds[1]);
37 exit(0);
38 }
39 return 0;
40 }
结果:
匿名管道读写规则:
阻塞方式:
1. 1、(若管道数据已空)调用read阻塞,直到有进程往里写数据
2. 2、(管道满)调用write阻塞,直到有进程读数据。
非阻塞方式:
3. 1、(管道数据已空)调用read返回-1;errno值为EAGAIN
4. 2、(管道满)调用write返回-1;errno值为EAGAIN
匿名管道的特点:
1. 用于有亲缘关系的进程;通常,一个管道有一个进程创建,然后由该进程调用fork,此后父、子进程之间就可用该管道。
2. 面向字节流
3. 生命周期随进程,进程退出,管道则释放。
4. 匿名管道半双工,数据只能向一个方向流动,若需要双方通信,需要建立两个管道。
5. 内核对管道操作进行同步与互斥。
命名管道
创建一个命名管道:
1、从命令行上创建:mkfifo filename
2、从程序创建:相关函数:int mkfifo (const char *filename,mode_t mode)
命名管道和匿名管道的区别:
- 创建匿名管道—-pipe函数;创建命名管道—mkfifo函数,打开用open
- 匿名管道只能用于具有亲缘关系的进程;命名管道可用于任意多个进程。
- 其余与匿名管道相同。
命名管道读写规则:
- (阻塞方式)若以‘r’方式打开一个命名管道时,它会阻塞在open函数中,直到有进程以‘w’方式打开;(反过来也是如此)
- 写端关闭时,读端立刻返回0;
- (非阻塞方式)如果以一个非阻塞‘r’方式打开,open函数立即返回;当在尝试读时,read返回0,当另一个进程以‘w’方式打开,read返回-1,虽然已‘w’方式打开,但是并未写数据;
如果以一个非阻塞‘w’方式打开,则打开失败。
例:用命名管道实现server&client通信:
1、创建一个命名管道myfifo;
2、实现server
3、实现client
结果: