最近正好有一些空余时间,在这里总结一下曾经使用过的Linux进程间通信的几种方法,贴出来帮助有需要的人,也有助于自己总结经验加深理解。上一次我们梳理了信号的相关知识,这一次梳理popen管道。
(一)概念
我们先来简单说一下管道的概念。
管道其实是一个很形象的表示术语。当一个进程把它的数据流连接到另一个进程时,我们就说他们构架了管道连接。管道通常不是杂乱的,是能单向。意思就是管道通常只能把一个进程的输出连接到另一个进程的输入,而不能把输入连接输入,输出连接输出。可以把进程想象成一个水管,而其中流动的水就是数据流,我们我们想把一个水管中的水引入另外一个水管,那么只能在他们中间架设小型的水管,而且被引入的水管一端一定要低于引入的水管一端。这个水管就像是管道,从一个进程的输出端连接到领一个进程的输入端。可以参考下面这张简图:
管道其实有很多种,最常用的应该就是shell中的"|"。他其实就是一个管道符,将前面的表达式的输出,引入后面表达式当作输入,比如我们常用的"ps aux|grep ssh"可以查看ssh的相关进程。
接下来我们来说一下popen管道。
我们在程序中经常使用popen管道,他可以在额外启动一个进程,执行我们设置的程序或脚本,可以接受当前线程的数据作为输入,以可以执行完成之后将输出导向当前线程。
(二)获取popen管道的数据流
我们现在就来尝试一下使用popen管道,先来尝试获取它的输出。我们可以使用popen()函数开启一个线程管道:
FILE* popen( const char* command, const char* open_mode );
其中,
--conmmand表示你要执行的命令,如"ls"。
--open_mode表示你需要的数据流权限,与fopen()的该参数类型相同。比如"r"表示只读,"w"只写。
当我们使用完成后,还需要使用pclose()函数关闭该管道:
int pclose( FILE* stream_to_close );
其中,
--stream_to_close就是你要关闭的管道数据流,也就是popen()函数的返回值。
那么我们就以非常简单的"pwd"命令为例,尝试写一个简单的popen管道程序,获取他的输出,我们通过读取popen()函数的返回值来达到这个目的。我们新建一个popen.c文件:
#include
#include
int main()
{
FILE* read_fp;
char read_buffer[64];
int read_buffer_len;
memset( read_buffer, 0, 64 );
/* 启动pwd命令,并获取他的输出。 */
read_fp = popen( "pwd", "r" );
if( read_fp == NULL )
{
printf( "error:can't exec popen.\n" );
return 1;
}
/* 将popen的输出读取出来,打印 */
read_buffer_len = fread( read_buffer, sizeof(char), 63, read_fp );
read_buffer[read_buffer_len] = '\0';
printf( "read:%s", read_buffer );
pclose( read_fp );
return 0;
}
然后我们编译执行一下它:
root@Server:/home/root/workspace/pipe/popen# gcc popen.c -o popen
root@Server:/home/root/workspace/pipe/popen# ./popen
read:/home/root/workspace/pipe/popen
可以看到read后面打印了当前路径,pwd程序的输出被导入到了程序里面。
(三)向popen管道输入数据流
现在我们尝试一下向一个popen管道输入程序的数据流,我们通过向popen的返回值写入字符串来达到这个目的。首先我们先编写一个程序printer.c,它负责将收到的信息打印出来,打印的同时显示当前进程的id号:
#include
#include
#include
int main()
{
char read_buffer[64];
/* 读取一个字符串然后打印 */
memset( read_buffer, 0, 64 );
scanf( "%s", read_buffer );
printf( "read(%d):%s\n", getpid(), read_buffer );
return 1;
}
我们先来试验一下我们的printer.c能否达到目的:
root@Server:/home/root/workspace/pipe/popen# gcc printer.c -o printer
root@Server:/home/root/workspace/pipe/popen# ./printer
hello
read(3374):hello
可以看到,当我们输入hello之后,程序同时打印了hello并且显示了当前进程好是3374,没有问题。那么我们现在来利用popen()函数启动这个程序,并且向他输入一个字符串,新建一个popen2.c:
#include
#include
#include
#include
int main()
{
FILE* write_fp;
char write_buffer[64];
memset( write_buffer, 0, 64 );
write_fp = popen( "./printer", "w" );
if( write_fp == NULL )
{
printf( "error:can't exec popen.\n" );
return 1;
}
strcpy( write_buffer, "popen2.c-info" );
fwrite( write_buffer, sizeof(char), strlen(write_buffer), write_fp );
printf( "write(%d):%s\n", getpid(), write_buffer );
pclose( write_fp );
return 0;
}
现在我们编译一下popen2.c并且执行它:
root@Server:/home/root/workspace/pipe/popen# gcc popen2.c -o popen2
root@Server:/home/root/workspace/pipe/popen# ./popen2
write(3394):popen2.c-info
read(3396):popen2.c-info
可以看到,popen2和printer分别打印了目标字符串"popen2-info"。同时他们的线程号不相同,这证明了popen2通过popen函数启动了printer并且将字符串作为输入流通过传递给了printer。
(四)一点小结
popen()的方式在程序中应用较多,因为它可以很简单的启动一个线程去执行另外一个程序,并且将数据传给它。不过popen()的过程是先启动shell,然后利用shell在启动我们的目标程序,即执行输入的command参数,在效率上可能会有很大影响。