前面我们讲了进程间通信的一种方式,共享内存。下面看一看另一种机制,匿名管道。
1.什么是管道
管道是一个进程的数据流到另一个进程的通道,即一个进程的数据输出作为另一个进程的数据输入,管道起到了桥梁的作用。
比如,在shell中输入命令:ls -l|grep string,ls和grep是两个进程,"|"符号表示管道,意思是执行ls -l进程,并将输出结果result_1,作为grep string进程的输入result_0,grep进程将result_0中存在字符串string的信息打印到屏幕。
2.管道的使用
1)popen函数:启用一个新进程,并可以向它传递数据,或者通过它接受数据。
FILE *popen(const char *command,conse char *open_mode);
command:运行的程序名和参数
open_mode:有两个值"r(只读)",“w(只写)”
“r”:可以获取新进程的输出
“w”:可以向新进程发送数据
返回值:返回输入输出文件流指针
2)pclose函数:关闭输入输出文件流指针
若调用该函数时,新进程仍然在运行,则pclose将等待,直至新进程结束。
返回值:返回新进程的退出码。
3.popen函数使用示例
下例循环读取read_fp输出文件流的内容,写入write_fp的输入文件流,直到输出流内容读完。
/* popen.c */
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
int main()
{
FILE *read_fp = NULL;
FILE *write_fp = NULL;
char buffer[BUFSIZ+1];
int chars_read = 0;
memset(buffer,'\0',sizeof(buffer)); //初始化缓冲区
read_fp = popen("ls -l","r");
write_fp = popen("grep rwxrwxrwx","w");
if(read_fp && write_fp)
{
chars_read = fread(buffer,sizeof(char),BUFSIZ,read_fp);
while(chars_read)
{
buffer[chars_read]='\0';
//把数据写入grep进程
fwrite(buffer,sizeof(char),chars_read,write_fp);
chars_read = fread(buffer,sizeof(char),BUFSIZ,read_fp);
}
//关闭文件流
pclose(read_fp);
pclose(write_fp);
exit(EXIT_SUCCESS);
}
printf("%d\n",2);
exit(EXIT_FAILURE);
}
输出结果:
[root@ pipe]# ./popen
drwxrwxrwx 1 root root 4096 Apr 14 2019 ./
drwxrwxrwx 1 root root 4096 Apr 14 08:42 ../
-rwxrwxrwx 1 root root 406 Apr 14 08:45 Makefile*
-rwxrwxrwx 1 root root 10208 Apr 14 08:46 pipe*
-rwxrwxrwx 1 root root 911 Apr 14 08:44 pipe.c*
-rwxrwxrwx 1 root root 5092 Apr 14 08:46 pipe.o*
-rwxrwxrwx 1 root root 9240 Apr 14 08:46 pipe_stdio*
-rwxrwxrwx 1 root root 858 Apr 14 08:44 pipe_stdio.c*
-rwxrwxrwx 1 root root 3824 Apr 14 08:46 pipe_stdio.o*
-rwxrwxrwx 1 root root 9776 Apr 14 2019 popen*
-rwxrwxrwx 1 root root 878 Apr 14 08:56 popen.c*
-rwxrwxrwx 1 root root 4580 Apr 14 2019 popen.o*
4.popen的原理及优缺点
当调用popen运行一个新进程时,它首先启动shell,然后将command参数传递给它。
优点:可以使用shell来分析命令字符串,启动非常复杂的shell命令。
缺点:不仅要启动一个新进程,还要启动一个shell,效率会比较低。
5.pipe函数的使用
int pipe(int file_description[2]);
file_description[2]:表示管道的输出输入端,输出端数据经过管道流到输入端,函数执行完后, 会将这个数组赋值。
file_description[1]表示管道输出端文件描述符
file_description[0]表示管道输入端文件描述符
返回值:0成功,-1失败
与popen不同的是,pipe函数是一个底层调用,不会启动shell。
popen是使用文件流(FILE)工作的,pipe使用的是文件描述符,相应的数据要用底层的read和write来读取和发送。
6.pipe函数使用示例
下例中,我们在父进程中创建一个管道,然后调用fork创建一个子进程。
此时,父进程的file_description[1]输出端,对应着子进程file_description[0]的输入端。
数据通过管道由父进程传到子进程。示例如下:
/* pipe.c */
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
int main()
{
int data_processed = 0;
const char data[]="Hello pipe!";
char buffer[BUFSIZ+1];
pid_t pid;
memset(buffer,'\0',sizeof(buffer));
int filedes[2];
if(pipe(filedes)==0)
{
//创建管道成功 //fork子进程
pid=fork(); if(pid==-1)
{
fprintf(stderr,"Fork failure");
exit(EXIT_FAILURE);
}
if(pid==0)
{
data_processed = read(filedes[0],buffer,BUFSIZ);
printf("read %d bytes:%s\n",data_processed,buffer);
exit(EXIT_SUCCESS);
}
else
{
data_processed = write(filedes[1],data,strlen(data));
printf("wrote %d bytes:%s\n",data_processed,data);
exit(EXIT_SUCCESS);
}
}
exit(EXIT_FAILURE);
}
输出结果:
[root@ pipe]# ./pipe
wrote 11 bytes:Hello pipe!
[root@ pipe]# read 11 bytes:Hello pipe!
7.管道用作标准输入和输出
我们知道标准的输入描述符为0,输出描述符为1,
为了使用已经定义好的标准程序,如od命令,从标准输入读入数据。
需要将管道的输入端描述符置为0,此时,我们需要用到一个辅助函数dup
dup函数:创建一个描述符,复制原有描述符参数的结构到新建的描述符。
int dup(int file_descriptor);
新的描述符规则是,使用最小的可用值。
要想使管道的输入描述符为标准输入描述符,我们可以先关闭文件描述符0,然后调用dup,
此时新建的描述符即为最小可用值0,标准输入描述符。
close(0);
dup(file_description[0]);
上例使用标准输入描述符改造后的示例如下:
/* pipe_stdio.c */
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
int main()
{
int data_processed = 0;
const char data[]="Hello pipe!";
int filedes[2];
pid_t pid;
if(pipe(filedes)==0)
{
pid = fork();
if(pid==-1)
{
fprintf("stderr","fork failure!\n");
exit(EXIT_FAILURE);
}
if(pid==0)
{
close(0);
dup(filedes[0]);
close(filedes[0]);
close(filedes[1]);
execlp("od","od","-c",0);
exit(EXIT_FAILURE);
}
else
{
close(filedes[0]);
data_processed = write(filedes[1],data,strlen(data));
close(filedes[1]);
printf("wrote %d bytes:%s\n",data_processed,data);
}
}
}
输出结果:
[root@ pipe]# ./pipe_stdio
wrote 11 bytes:Hello pipe!
[root@ pipe]# 0000000 H e l l o p i p e !
0000013
8.匿名管道需要注意的问题
1)当管道没有关闭时,若没有数据可读,read调用会阻塞
2)当管道关闭时,read调用会返回0
3)匿名管道通信,进程间必须是父子关系。
Makefile:
APP_NAME = popen pipe pipe_stdio
APP_OBJS = popen.o pipe.o pipe_stdio.o
CC = gcc
INC = ./
CFLAG += -g
.PHONY : all
all : $(APP_NAME)
popen : popen.o
$(CC) $(CFLAG) $^ -o $@
pipe : pipe.o
$(CC) $(CFLAG) $^ -o $@
pipe_stdio : pipe_stdio.o
$(CC) $(CFLAG) $^ -o $@
%.o : %.c
$(CC) -c $(CFLAG) $^ -o $@
.PHONY : clean
clean :
rm -f *.o
rm -f $(APP_NAME) $(APP_OBJS)