什么是管道文件
首先,管道是Unix中最古老的进程间通信的形式。它用于进程间的单向通信
那么具体是怎样实现的呢?从标题里就可以发现,是基于文件
既然一个文件可以被多个进程打开,那么不妨将文件作为两个进程通信的媒介。但是一般位于磁盘上的文件,IO效率相比于CPU,内存之类的读写速度慢了几个数量级,但文件是可以被加载到内存中的,而专门建立在内存中,而没有磁盘文件,专门用于进程间通信的内存级文件,我们就叫它管道文件

管道文件由内核维护

管道文件是单向的,可以是父进程->子进程,也可以子进程->父进程
管道读写规则
- 写端未关闭,但读端无数据可读时
- (默认)O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
- O_NONBLOCK enable:- read调用返回- -1,- errno值为- EAGAIN。
 
- 读端未关闭,但写端写入管道已经写满时
- (默认) O_NONBLOCK disable:write调用阻塞,直到有进程读走数据
- O_NONBLOCK enable:- wrtie调用返回- -1,- errno值为- EAGAIN
 
- 若写端关闭,则read返回0
- 若读端关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出
原子性
头文件提供了宏PIPE_BUF,规定了保证原子性读写操作的最大字节数
- 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
- 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
管道特点
- 管道提供流式服务
- 一般而言,进程退出,管道释放,所以管道的生命周期随进程
- 一般而言,内核会对管道操作进行同步与互斥
- 一个管道只有一个通信方向,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
匿名管道
匿名管道主要用于父子进程间的通信
用到的接口是来自<unistd.h>的接口pipe
int pipe(int pipefd[2]);
可以看到有一个输出型参数pipefd数组,其中规定pipefd[0]储存了管道文件的读端fd,pipefd[1]储存了管道文件的写端fd
要利用管道通信时,必须用close一方关闭写端而另一端关闭读端
示例 子进程发送报文,父进程接受模型
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 
 | #include <iostream>#include <cstdlib>
 #include <unistd.h>
 #include <string>
 #include <cstring>
 #include <sys/types.h>
 #include <sys/wait.h>
 
 #define N 2
 #define NUM 1024
 
 using namespace std;
 
 
 void Writer(int wfd)
 {
 string s = "hello,I am child";
 pid_t self = getpid();
 int number = 5;
 
 char buffer[NUM] = {0};
 while(number--)
 {
 buffer[0] = 0;
 snprintf(buffer,sizeof(buffer),"%s-%d-%d\n",s.c_str(),self,number);
 
 
 write(wfd,buffer,strlen(buffer));
 
 sleep(1);
 }
 }
 
 
 void Reader(int rfd)
 {
 char buffer[NUM];
 
 while(true)
 {
 buffer[0] = 0;
 ssize_t n = read(rfd,buffer,sizeof(buffer));
 if(n > 0)
 {
 buffer[n] = 0;
 cout<<"father get a message >> "<<buffer<<endl;
 }
 }
 }
 
 int main()
 {
 int pipefd[N] = {0};
 
 int n = pipe(pipefd);
 if(n<0) return 1;
 
 pid_t id = fork();
 if(id  < 0) return 2;
 else if(id == 0)
 {
 
 close(pipefd[0]);
 
 Writer(pipefd[1]);
 
 close(pipefd[1]);
 exit(0);
 }
 else
 {
 close(pipefd[1]);
 
 Reader(pipefd[0]);
 
 pid_t rid = waitpid(id,nullptr,0);
 
 if(id <0) return 3;
 
 close(pipefd[0]);
 }
 
 return 0;
 }
 
 | 
小小项目–进程池
详见此博客🔗
命名管道
匿名管道无非实现不相关进程(无亲缘关系)的进程间通信,因此要用到命名管道来实现这个功能
命名管道文件,即FIFO文件,是一种用于不相关进程间通信的特殊类型的文件
命令行上创建
使用mkfifo可以在命令行上创建命名管道
例mkfile fifo_file
程序内创建和删除
使用接口mkfifo创建,注:要同时引用头文件sys/types.h和sys/stat.h
函数声明如下
int mkfifo(const char *pathname, mode_t mode);
- 返回值:成功时返回0,失败时返回-1,并设置errno
- pathname:文件名(当前目录),或者路径+文件名
- mode新创建的管道文件的权限,一般用- 0644或- 0664
使用unlink删除文件,引用自头文件<unistd.h>
int unlink(const char *pathname);
- 返回值:成功时返回0,失败时返回-1,并设置errno
- pathname:文件名(当前目录),或者路径+文件名
使用
由一个进程写模式打开,同时由另一个进程读模式打开,便可建立进程间通信,其余操作与匿名管道相同
命名管道的打开规则
- 读模式打开- FIFO文件时- 
- O_NONBLOCK disable:阻塞等待直到有相应进程为写而打开该- FIFO
- O_NONBLOCK enable:立刻返回成功
 
- 写模式打开- FIFO文件时- 
- O_NONBLOCK disable:阻塞直到有相应进程为读而打开该- FIFO
-  O_NONBLOCK enable:立刻返回失败,错误码为- ENXIO
 
示例 命名管道实现 客户端 向 服务端通信
makefile
小技巧:这里在最前面使用伪目标all,这样在使用make命令时能编译多个目标文件
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | .PHONY:allall:sever client
 
 sever:sever.cpp
 g++ -o $@ $^ -std=c++11
 client:client.cpp
 g++ -o $@ $^ -std=c++11
 
 .PHONY:cl
 cl:
 rm -f sever client
 
 | 
comm.hpp
使用同一个头文件能方便地统一fifo文件的路径,以及统一退出码的约定等
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | #pragma once
 #include <iostream>
 #include <errno.h>
 #include <cstring>
 #include <cstdlib>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <unistd.h>
 
 #define FIFO_NAME "./myfifo"
 #define MODE 0664
 
 enum
 {
 FIFO_CREAT_ERR = 1,
 FIFO_DELETE_ERR,
 FIFO_OPEN_ERR
 };
 
 | 
sever.cpp
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 
 | #include "comm.hpp"
 using namespace std;
 
 int main()
 {
 
 int n = mkfifo(FIFO_NAME,MODE);
 if(n == -1)
 {
 perror("mkfifo");
 exit(FIFO_CREAT_ERR);
 }
 
 
 int fd = open(FIFO_NAME,O_RDONLY);
 if(fd == -1)
 {
 perror("open");
 exit(FIFO_OPEN_ERR);
 }
 
 
 cout<<"[@]sever start running"<<endl;
 while(true)
 {
 char buffer[1024] = {0};
 int sz = read(fd,buffer,sizeof(buffer)-1);
 if(sz > 0)
 {
 buffer[sz] = 0;
 cout<<"client say# "<<buffer<<endl;
 }
 else if(sz == 0)
 {
 
 cout<<"client quit, sever will quie later..." <<endl;
 break;
 }
 else break;
 }
 
 
 close(fd);
 int m = unlink(FIFO_NAME);
 if(m == -1)
 {
 perror("unlink");
 exit(FIFO_DELETE_ERR);
 }
 
 return 0;
 }
 
 | 
client.cpp
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 
 | #include "comm.hpp"
 using namespace std;
 
 int main()
 {
 int fd = open(FIFO_NAME,O_WRONLY);
 if(fd <0)
 {
 perror("open");
 exit(FIFO_OPEN_ERR);
 }
 
 string line;
 while(true)
 {
 cout<<"Please enter@ ";
 getline(cin,line);
 
 write(fd,line.c_str(),line.size());
 }
 
 close(fd);
 return 0;
 }
 
 
 | 
实现效果
