1. 简介
分类
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 |
-1 |
失败 |
2 |
0 |
成功 |
示例
#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);
}
#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]);
}
}
本质
文件描述符
|
文件描述符 |
文件流 |
数据 |
int 整数 |
FILE 指针 |
标准 |
POSIX |
ANSI C |
打开 |
open |
fopen |
关闭 |
close |
fclose |
读 |
read |
fread |
写 |
write |
fwrite |
定位 |
lseek |
fseek |
- 文件流是文件描述符之上的封装。文件流通过增加缓冲区减少读写系统调用次数来提高读写效率。在进程的用户空间封装的
FILE
结构,以提高可移植性和效率。
2.3 管道复制
分类 |
文件描述符 |
文件号 |
标准输入 |
STDIN_FILENO |
0 |
标准输出 |
STDOUT_FILENO |
1 |
标准出错信息 |
STDERR_FILENO |
2 |
内核为每个进程创建的文件描述符。
① 函数int dup(int 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);
}
特点
必须是亲缘进程之间
案例
-
ps_self
打印自身的父子进程信息
-
ps_other
执行参数中的命令,并且打印参数中的命令相关的父子进程
2.4 FIFO管道/命名管道
① 创建命名管道int mkfifo(pathname,mode)
No. |
参数 |
含义 |
1 |
pathname |
文件路径,文件必须不存在 |
2 |
mode |
模式 |
No. |
返回值 |
含义 |
1 |
0 |
成功 |
2 |
非零 |
失败 |
② 打开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 |
其他 |
文件描述符 |
特点
- 可以是非亲缘进程之间
- 读写必须同时执行,否则阻塞。
案例
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 概念图解
3.3.2 原理图解
3.3 父子进程双向管道
文件描述符
Linux内核使用三个关联的数据结构,表示打开的文件。
dup()/dup2()原理图