图片来自网络
1. 简介
查看管道命令:man 7 pipe
分类
匿名管道
FIFO管道/命名管道
2 匿名管道
2.1 单工管道
程序进程与Shell命令行进程单项通信。
① 打开管道FILE* popen (const char *command, const char *open_mode)
参数
No.
参数
含义
1
command
命令行字符串
2
open_mode
"r"只读"w"只写
返回值
No.
返回值
含义
1
NULL
文件描述符
2
非NULL
打开失败
② 读取size_t fread ( void *buffer, size_t size, size_t count, FILE *stream)
参数
No.
参数
含义
1
buffer
用于接收数据的内存地址
2
size
读取每个数据项的字节数
3
count
数据项个数
4
stream
输入流
返回值
No.
返回值
含义
1
>count
出错
2
正数
真实读取的数据项个数
③ 写入size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream)
参数
No.
参数
含义
1
buffer
写入数据的内存地址
2
size
写入数据项的字节数
3
count
写入数据项的个数
4
stream
目标文件指针
返回值
No.
返回值
含义
1
>count
出错
2
正数
真实写入的数据项个数
④ 关闭管道int pclose(FILE *stream);
参数
No.
参数
含义
1
stream
文件描述符
返回值
No.
返回值
含义
1
-1
失败
2
0
成功
示例
write
#include
#include
#include
int main(){
FILE* fd = popen("wc","w");
//FILE* fd = popen("ls -l","r");
//char str[] = "123 456";
char str[] = "123 456\n";
size_t n = fwrite(str,sizeof(char),sizeof(str),fd);
if(n > sizeof(str)){
fprintf(stderr,"FILE:%d,LINE:%d-fwrite error",__FILE__,__LINE__);
exit(EXIT_FAILURE);
}
pclose(fd);
}
read
#include
#include
#include
int main(){
FILE* fd = popen("ps -ef","r");
//FILE* fd = popen("ls -l","r");
char buf[BUFSIZ];
size_t count = 0;
printf("read data:\n");
do{
memset(buf,'\0',BUFSIZ);
size_t n = fread(buf,sizeof(char),BUFSIZ-1,fd);
if( n > BUFSIZ - 1 ){
perror("fread error");
exit(EXIT_FAILURE);
}
count += n;
printf("\n%d:\n%s",n,buf);
}while(!feof(fd));
printf("total size:%ld\n",count);
pclose(fd);
}
本质
启动shell和命令两个进程,从命令进程中读/写文件流。
解决exec和system无法返回输出数据问题
特点
方便使用系统自带功能,并且可以执行比较复杂Shell
默认启动两个进程,效率较低。
操作
管道
文件
打开
popen()
fopen()
关闭
pclose()
fclose()
2.2 半双工管道
① 创建管道int pipe(int filedes[2])
参数
No.
参数
含义
1
filedes[0]
读
2
filedes[1]
写
返回值
No.
返回值
含义
1
-1
失败
2
0
成功
② 读取ssize_t write(int fd, const void *buf, size_t nbyte)
参数
No.
参数
含义
1
fd
文件描述符
2
buf
写入数据的内存单元
3
nbyte
写入文件指定的字节数
返回值
No.
返回值
含义
1
-1
出错
2
正数
写入的字节数
③ 写入ssize_t read(int fd, void *buf, size_t count)
参数
No.
参数
含义
1
fd
文件描述符
2
buf
读取数据的内存单元
返回值
No.
返回值
含义
1
-1
出错
2
0
无数据
3
正数
读取的字节数
④ 控制int fcntl(int fd, int cmd, long arg)
如果管道是空的,read()默认是阻塞
参数
No.
参数
含义
1
fd
文件描述符
2
cmd
F_GETFL:获取文件描述符状态;F_SETFL:设置文件描述符状态;
3
arg
O_NONBLOCK:非阻塞;O_BLOCK:阻塞
把文件描述符改为非阻塞的fcntl(filedes,F_SETFL,O_NONBLOCK);
⑤ 关闭管道close(filedes)
示例
单进程读写管道
#include
#include
#include
int main(){
int fd[2];
pipe(fd);
char in[] = "Hello pipe";
write(fd[1],in,sizeof(in));
printf("write:%s\n",in);
char out[sizeof(in)]={0};
ssize_t n = read(fd[0],out,sizeof(out));
if(-1 == n){
perror("read error");
return -1;
}
printf("read:%s\n",out);
close(fd[0]);
close(fd[1]);
}
两进程读写
#include
#include
#include
int main(){
int fd[2];
pipe(fd);
if(!fork()){// child
char in[] = "Hello pipe";
write(fd[1],in,sizeof(in));
printf("child %d write:%s\n",getpid(),in);
}else{// parent
char out[BUFSIZ]={0};
ssize_t n = read(fd[0],out,sizeof(out));
if(-1 == n){
perror("read error");
return -1;
}
printf("parent %d read:%s\n",getpid(),out);
}
close(fd[0]);
close(fd[1]);
}
两进程先读后写
#include
#include
#include
int main(){
int fd[2];
pipe(fd);
if(!fork()){// child
char in[] = "Hello pipe";
sleep(3);
write(fd[1],in,sizeof(in));
printf("child %d write:%s\n",getpid(),in);
}else{// parent
char out[BUFSIZ]={0};
ssize_t n = read(fd[0],out,sizeof(out));
if(-1 == n){
perror("read error");
return -1;
}
printf("parent %d read:%s\n",getpid(),out);
}
close(fd[0]);
close(fd[1]);
}
两进程先读后写(非阻塞)
#include
#include
#include
#include
int main(){
int fd[2];
pipe(fd);
if(!fork()){// child
char in[] = "Hello pipe";
sleep(3);
write(fd[1],in,sizeof(in));
printf("child %d write:%s\n",getpid(),in);
}else{// parent
fcntl(fd[0],F_SETFL,O_NONBLOCK);
char out[BUFSIZ]={0};
ssize_t n = read(fd[0],out,sizeof(out));
if(-1 == n){
perror("read error");
return -1;
}
printf("parent %d read:%s\n",getpid(),out);
}
close(fd[0]);
close(fd[1]);
}
更加规范的写法(关闭不需要的管道)
#include
#include
#include
#include
int main(){
int fd[2];
pipe(fd);
if(!fork()){// child
close(fd[0]);
char in[] = "Hello pipe";
sleep(3);
write(fd[1],in,sizeof(in));
printf("child %d write:%s\n",getpid(),in);
close(fd[1]);
}else{// parent
close(fd[1]);
fcntl(fd[0],F_SETFL,O_NONBLOCK);
char out[BUFSIZ]={0};
ssize_t n = read(fd[0],out,sizeof(out));
if(-1 == n){
perror("read error");
return -1;
}
printf("parent %d read:%s\n",getpid(),out);
close(fd[0]);
}
}
2.3 FIFO管道/命名管道
① 创建命名管道int mkfifo(pathname,mode)
参数
No.
参数
含义
1
pathname
文件路径,文件必须不存在
2
mode
模式
返回值
No.
返回值
含义
1
0
成功
2
非零
失败
示例
#include
#include
int main(){
if(-1 == mkfifo("/tmp/test",0644)){
perror("mkfifo error");
return 1;
}
}
注意:
管道文件通常在/tmp目录下创建。
管道文件大小通常是0
② 打开FIFO文件int open(const char *path, int mode)
参数
No.
参数
含义
1
pathname
文件路径
2
mode
模式
模式
No.
模式
含义
1
O_RDONLY
阻塞只读
2
O_RDONLY | O_NONBLOCK
非阻塞只读
3
O_WRONLY
阻塞只写
4
O_WRONLY | O_NONBLOCK
非阻塞只写
返回值
No.
返回值
含义
1
-1
失败
2
其他
文件描述符
示例
阻塞读
#include
#include
#include // open() O_RDONLY
using namespace std;
#define BUFSIZE 258
int main(){
// 打开命名管道
int fd = open("/tmp/fifo",O_RDONLY);
if(-1 == fd){
perror("open error");
return EXIT_FAILURE;
}else{
cout << "filo open ok" << endl;
}
// 读取字符串
char buffer[BUFSIZE] = {0};
read(fd,buffer,BUFSIZE);
// 打印读取的字符串
cout << buffer << endl;
// 关闭管道
close(fd);
return EXIT_SUCCESS;
}
堵塞写
#include
#include // open() O_WRONLY
#include
using namespace std;
int main(){
// 打开命名管道
int fd = open("/tmp/fifo",O_WRONLY);
if(-1 == fd){
perror("open error");
return EXIT_FAILURE;
}else{
cout << "filo open ok" << endl;
}
// 读取字符串
string str;
getline(cin,str);
// 写入管道
write(fd,str.c_str(),str.size()+1);
// 关闭管道
close(fd);
return EXIT_SUCCESS;
}
写非阻塞
说明:
只需要在open()添加O_NONBLOCK。
写open()非阻塞,读open()阻塞的情况。读open()需要先执行,否则,写open()会出现No such device or address。
#include
#include // open() O_WRONLY O_NONBLOCK
#include
using namespace std;
int main(){
// 打开命名管道
int fd = open("/tmp/fifo",O_WRONLY|O_NONBLOCK);
if(-1 == fd){
perror("open error");
return EXIT_FAILURE;
}else{
cout << "filo open ok" << endl;
}
// 读取字符串
string str;
getline(cin,str);
// 写入管道
write(fd,str.c_str(),str.size()+1);
// 关闭管道
close(fd);
return EXIT_SUCCESS;
}
读非阻塞
说明:
只需要在open()添加O_NONBLOCK。
写open()阻塞,读open()非阻塞的情况。读read()需要处理写open()未执行(read()返回0)和读不到数据(写open()打开但是没有写数据,read()返回-1)的情况。
#include
#include
#include // open() O_RDONLY O_NONBLOCK
using namespace std;
#define BUFSIZE 258
int main(){
// 打开命名管道
int fd = open("/tmp/fifo",O_RDONLY|O_NONBLOCK);
if(-1 == fd){
perror("open error");
return EXIT_FAILURE;
}else{
cout << "filo open ok" << endl;
}
// 读取字符串
char buffer[BUFSIZE] = {0};
int n = -1;
while(n<=0){
n=read(fd,buffer,BUFSIZE);
}
// 打印读取的字符串
cout << buffer << endl;
// 关闭管道
close(fd);
return EXIT_SUCCESS;
}
特点
可以是非亲缘进程之间
FIFO首先会阻塞在open(),等待读写文件的文件描述符都打开。接着阻塞在read()/write()操作,读写操作需要同时执行。
案例
FIFO工具箱
3. 通信分类
只写单工
只读单工
半双工
全双工
No.
类型
创建/打开
关闭
读
写
1
单工
popen()
pclose
read()
write()
2
半双工
pipe()/open()
close()
read()
write()
3
FIFO半双工
mkfifo()/open
close()/unlink()
read()
write()
4
全双工
socketpair()
close()
read()
write()
3.1 单进程管道
单进程管道
管道通常用于进程间通信
3.2 父子进程单向管道
3.3.1 概念图解
父子进程管道
父进程关闭fd[0] 子进程关闭fd[1]
父子进程单向管道
3.3.2 原理图解
3.3 父子进程双向管道
父子进程双向管道
4. 文件描述符
4.1 Linux文件读写与标准C的文件读写
文件描述符
文件描述符
文件流
数据
int整数
FILE指针
标准
POSIX
ANSI C
打开
open
fopen
关闭
close
fclose
读
read
fread
写
write
fwrite
定位
lseek
fseek
文件流是文件描述符之上的封装。文件流通过增加缓冲区减少读写系统调用次数来提高读写效率。在进程的用户空间封装的FILE结构,以提高可移植性和效率。
4.2 文件描述符原理
Linux内核使用三个关联的数据结构,表示打开的文件。
打开不同的文件
打开相同的文件
父子进程共享文件
4.3 命令lsof
lsof(list open files):列出当前系统打开文件
列名含义
No.
列名
含义
1
COMMAND
进程的名称
2
PID
进程标识符
3
USER
进程所有者
4
FD
文件描述符,应用程序通过文件描述符识别该文件。如cwd、txt等
5
TYPE
文件类型,如PIPE、DIR、REG等
6
DEVICE
指定磁盘的名称
7
SIZE
文件的大小
8
NODE
索引节点(文件在磁盘上的标识)
9
NAME
打开文件的确切名称
常用方法
No.
命令
作用
1
lsof 文件名
查看文件打开信息
2
lsof -d 文件描述符
查看文件描述符信息
3
lsof -p PID
查看进程PID打开的文件信息
4.3 文件描述符复制
分类
文件描述符
文件号
标准输入
STDIN_FILENO
0
标准输出
STDOUT_FILENO
1
标准出错信息
STDERR_FILENO
2
内核为每个进程创建的文件描述符。
① 函数int dup(int oldfd)
参数
No.
参数
含义
1
oldfd
旧文件描述符
返回值
No.
返回值
含义
1
-1
失败
2
其他
新文件描述符
② 函数int dup2(int oldfd, int newfd)
参数
No.
参数
含义
1
oldfd
旧文件描述符
2
newfd
新文件描述符
返回值
No.
返回值
含义
1
-1
失败
2
其他
最小及尚未使用的文件描述符
示例
复制标准输出
新文件描述符与旧文件描述符不同,但是具备旧文件描述符功能
#include
#include
#include
#include
int main(){
int fd = dup(STDOUT_FILENO);
fprintf(fdopen(fd,"w"),"%d printf:Hello dup\n",fd);
}
复制文件描述符
新文件描述符与旧文件描述符不同,但是具备旧文件描述符功能
#include
#include
#include
#include
#include
#define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)
int main(){
int fd = open("./test",O_CREAT|O_RDWR,FILE_MODE);
char str[]="Hello dup\n";
write(fd,str,sizeof(str));
int cp_fd = dup(fd);
printf("copy %d to %d",fd,cp_fd);
write(cp_fd,str,sizeof(str));
//fprintf(fdopen(fd,"w"),"%d printf:Hello dup\n",fd);
close(fd);
}
把文件描述符重定向(复制)到标准输出
printf()直接输出到文件中,不再输出到终端
#include
#include
#include
#include
#include
#define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)
int main(){
int fd = open("./test",O_CREAT|O_RDWR,FILE_MODE);
char str[]="Hello dup\n";
dup2(fd,STDOUT_FILENO);
printf("%d printf:Hello dup\n",fd);
}
把文件描述符重定向(复制)到标准输出,并且输出后还原
注意把标准输出流从文件重定向(复制)回终端,需要清除缓冲区。
#include
#include
#include
#include
#include
#define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)
int main(){
int save_fd = dup(STDOUT_FILENO);
int fd = open("./test",O_CREAT|O_RDWR,FILE_MODE);
if(-1 == dup2(fd,STDOUT_FILENO)){
perror("dup2 error0");
return 1;
}
close(fd);
printf("%d printf:Hello dup\n",fd);
fflush(stdout);// 一定要清除缓冲区,否则会输出到终端
if(-1 == dup2(save_fd,STDOUT_FILENO)){
perror("dup2 error");
return 1;
}
close(save_fd);
printf("%d printf:this is save\n",save_fd);
}
特点
必须是亲缘进程之间
dup()/dup2()原理图