Linux 进程间通信基础(二)--popen管道

最近正好有一些空余时间,在这里总结一下曾经使用过的Linux进程间通信的几种方法,贴出来帮助有需要的人,也有助于自己总结经验加深理解。上一次我们梳理了信号的相关知识,这一次梳理popen管道。

(一)概念

我们先来简单说一下管道的概念。

管道其实是一个很形象的表示术语。当一个进程把它的数据流连接到另一个进程时,我们就说他们构架了管道连接。管道通常不是杂乱的,是能单向。意思就是管道通常只能把一个进程的输出连接到另一个进程的输入,而不能把输入连接输入,输出连接输出。可以把进程想象成一个水管,而其中流动的水就是数据流,我们我们想把一个水管中的水引入另外一个水管,那么只能在他们中间架设小型的水管,而且被引入的水管一端一定要低于引入的水管一端。这个水管就像是管道,从一个进程的输出端连接到领一个进程的输入端。可以参考下面这张简图:

Linux 进程间通信基础(二)--popen管道_第1张图片

管道其实有很多种,最常用的应该就是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参数,在效率上可能会有很大影响。

你可能感兴趣的:(Linux程序设计)