我们知道在两个进程间发送消息的非常简单的方法:使用信号,我们创建通知事件,通过它引起响应,但传送的信息只限于一个信号值,所以在此我们将介绍管道,通过它进程之间可以交换更有用的数据。
一、什么是管道:通常是把一个进程得输出通过管道连接到另一个进程得输入。对于shell命令来说,命令的连接是通过管道字符来完成的,如下:
cmd1 | cmd2 shell负责安排两个命令的标准输入和标准输出
- cmd1的标准输入来自终端键盘
- cmd1的标准输出传递给cmd2,作为它的标准输入
- cmd2的标准输出连接到终端屏幕上
二、进程管道
最简单的在两个程序之间传递数据的方法是使用popen和pclose函数
头文件 #include<stdio.h>
FILE *popen(const char *command, const char *open_mode);
int pclose(FILE *stream_to_close);
1、popen函数:
(1)允许一个程序将另一个程序作为新进程来启动,并可以传递数据给它或者通过它接收数据。
(2)command字符串是要运行的程序名和相应的参数
(3)open_mode必须是“r” 或者“w”,这意味着我们不能调用另一个程序并同时对它进行读写操作。popen函数在失败是返回一个空指针,如果想通过管道实现双向通信,最普通的结局方法就是使用两个管道,每个管道负责一个方向的数据流
- 如果是“r”,被调用程序的输出就可以被调用者程序使用,调用程序利用popen函数返回FILE *文件流指针,就可以通过常用的stdio库函数(如fread)来读取调用程序的输出。
- 如果是“w” 调用程序就可以用fwrite调用向被调用程序发送数据,而被调用程序可以在自己的标准输入上读取这些数据。被调用的程序通常不会意识到自己正在从另一个进程读取数据,它只是在标准输入流上读取数据,然后做出相应的操作
2、pclose函数:
(1)用popen启动的进程结束时,我们可以用pclose函数关闭与之关联的文件流。pclose调用只在popen启动的进程结束后才返回,如果调用pclose时它仍在运行,pclose调用将等待该进程的结束
(2)pclose调用的返回值通常时它所关闭的文件流所在进程的退出码。如果调用进程在调用pclose之前执行一个wait语句,被调用进程的退出状态就会丢失,因为被调用进程已结束。此时,pclose将返回-1,并设置errno为ECHILD。
- 现在我们写一个简单的读取外部程序的输出的代码:
//捕获uname命令的输出
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#define BUFSIZE 128
int main()
{
FILE *read_fp; //设置文件流指针
char buffer[BUFSIZE];
int chars_read;
memset(buffer,'\0',sizeof(buffer)); //全部清零
read_fp = popen("uname -a","r");
if(read_fp != NULL)
{
chars_read = fread(buffer,sizeof(char),BUFSIZE,read_fp);
if(chars_read > 0)
{
printf("Outpup was:-\n%s",buffer);
}
pclose(read_fp);
exit(EXIT_SUCCESS);
}
exit(EXIT_FAILURE);
}
结果显示:
uname -a 的作用是打印系统信息,包括计算机型号、操作系统名称、版本和发行号,以及计算机的网络名。
- 将输出送往popen
代码实现:
//将输出送往外部程序
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#define BUFSIZE 128
int main()
{
FILE *write_fp;
char buffer[BUFSIZE];
sprintf(buffer, "Once upon a time, there was ...\n");
write_fp = popen("od -c","w");
if(write_fp != NULL)
{
fwrite(buffer,sizeof(char),strlen(buffer),write_fp);
pclose(write_fp);
exit(EXIT_SUCCESS);
}
exit(EXIT_FAILURE);
}
结果展示:
程序使用带参数“w”的popen启动od -c命令,这样就可以向该命令发送数据了,然后它给od -c命令发送一个字符串,该命令接收并处理它,最后把处理结果打印到自己的标准输出上。
3、pipe调用
前面所讲的popen函数属于高级的函数,现在讲述底层的pipe函数,通过pipe函数在两个程序之间传递数据不需要启动一个shell来解释请求的命令,它同时还提供了对读写数据的更多控制。
- 头文件 <unistd.h>
- int pipe(int file_descriptor[2]); //参数是一个由两个整数类型的文件描述符组成的数组的指针。该函数在数组中填上两个新的文件描述符后返回0,如果失败则返回-1并设置errno来表明失败的原因。
- EMFILE:进程使用的文件描述符过多
- ENFILE:系统的文件表已满
- EFAULT:文件描述符无效
两个返回的文件描述符以一种特殊的方式连接起来,写到file_descriptor[1]的所有数据都可以从file_descriptor[0]读回来,数据基于先进先出的原则进行处理,这意味着如果你把字节1,2,3写到file_descriptor[1],从file_descriptor[0]读取到的数据也会是1,2,3。
特别注意的是:这里使用的是文件描述符而不是文件流,所以我们必须用底层的read和write调用来访问数据,而不是文件流库函数fread和fwrite。
注:file_pipe数组的用法,它的地址被当作参数传递给pipe函数
代码实现:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#define BUFSIZE 128
int main()
{
int data_processed;
int file_pipes[2];
const char some_data[] = "123";
char buffer[BUFSIZE];
memset(buffer,'\0',sizeof(buffer));
if(pipe(file_pipes) == 0)
{
data_processed = write(file_pipes[1],some_data,strlen(some_data));
printf("Wrote %d bytes\n",data_processed);
data_processed = read(file_pipes[0], buffer,BUFSIZE);
printf("Read %d bytes :%s\n",data_processed,buffer);
exit(EXIT_SUCCESS);
}
exit(EXIT_SUCCESS);
}
结果展示:
这个程序用数组file_pipes[]中的两个文件描述符创建一个管道,然后它用文件描述符file_pipes[1]向管道中写数据,再从file_pipes[0]读回数据,注:管道有一些内置的缓存区,它在write和read调用之间保存数据。