首页 > 代码库 > Linux环境编程之IPC进程间通信(二):管道

Linux环境编程之IPC进程间通信(二):管道

管道作为最古老的进程间通信方法,它有以下几个特点:

1、在所有的UNIX实现中都存在。

2、没有名字,因此只能由有亲缘关系的进程使用。

3、它由函数pipe创建,read和write函数访问,但只提供单路(单向)数据流。

<span style="font-size:14px;">#include <unistd.h>
int pipe(int fd[2]);				返回:若成功则为0,若出错则为-1</span>

经由参数fd返回两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。fd[1]的输出是fd[0]的输入。

宏S_ISFIFO可用于确定一个描述符或文件是管道还是FIFO。它的唯一参数是stat结构的st_mode成员。对于管道来说,这个stat结构是由fstat函数填写的。对于FIFO来说,这个结构是由fstat、lstat或stat函数填写的。

单进程使用管道的情况一般如图所示:


图 单个进程中的管道

尽管管道是由单个进程创建的,它却很少在单个进程内使用。通常,调用pipe的进程接着调用fork,这样就创建了从父进程到子进程或反向的IPC通道。如图所示;


图 单个进程内的管道,刚刚fork后

对于从父进程到子进程的管道,父进程关闭管道的读端fd[0],子进程关闭写端fd[1]。结果如图所示:


图 从父进程到子进程的管道

从父进程到子进程的管道,并且父进程经由该管道向子进程传送数据。程序示例如下:

<span style="font-size:14px;">#include <unistd.h>
#define MAXLINE 4096

int
main(void)
{
	int	n;
	int fd[2];
	pid_t pid;
	char line[MAXLINE];

	if(pipe(fd) < 0){
		printf("pipe error");	
		return -1;
	}

	if((pid = fork()) < 0){
		printf("fork error");	
		return -1;
	}else if(pid > 0){      /*parent 父进程*/
		close(fd[0]);	
		write(fd[1], "hello world", 12);
	}else{                 /*child   子进程*/
		close(fd[1]);	
		n = read(fd[0], line, MAXLINE);
		write(STDOUT_FILENO, line, n);
	}
	exit(0);
}</span>
<span style="font-size:14px;">编译: gcc pipe.c -o pipe
测试:./pipe
结果:hello world</span>

上面所说的管道都是半双工的即单向的,只提供一个方向的数据流。当需要一个双向数据流时,必须创建两个管道,每个方向一个。实际步骤如下:

1、创建管道1和管道2

2、fork

3、父进程关闭管道1的读出端、关闭管道2的写入端

3、子进程关闭管道1的写入端、关闭管道2的读出端。

完成上述步骤后的管道布局如下图所示。

图  提供一个双向数据流的两个管道
有了双向数据流后,我们就可以完成客户-服务器的程序例子了。其实现如下图所示:
图 使用两个管道实现的客户-服务器例程
根据上面提到的创建两个管道的步骤以及客户-服务器的图示,可以利用管道编写客户-服务器例程代码如下:
mainpipe.c
<span style="font-size:14px;">#include "pipe.h"

extern void client(int, int);
extern void server(int, int);

int
main(int argc, char *argv[])
{
	int		pipe1[2], pipe2[2];
	pid_t	pid;

	pipe(pipe1);
	pipe(pipe2);

	if((pid = fork()) < 0){
		printf("fork error.\n");
		return  -1;
	}else if(pid == 0){				/*child*/
		close(pipe1[1]);
		close(pipe2[0]);

		server(pipe1[0], pipe2[1]);
		exit(0);
	}else{							/*parent*/
		close(pipe1[0]);
		close(pipe2[1]);

		client(pipe2[0], pipe1[1]);
		waitpid(pid, NULL, 0);	/*wait for child to terminate*/
		exit(0);
	}
}</span>
server.c
<span style="font-size:14px;">#include "pipe.h"

void 
server(int readfd, int writefd)
{
	int		fd;
	ssize_t	n;
	char 	buff[MAXLINE+1];

	/*read pathname frome IPC channel*/
	if((n = read(readfd, buff, MAXLINE)) == 0){
		printf("end-of-file while reading pathname.\n");
		return ;
	}
	buff[n] = '\0';			/*null terminate pathname*/

	if((fd = open(buff, O_RDONLY)) < 0){
		/*error:must tell client*/	
		snprintf(buff+n, sizeof(buff)-n, ":can't open, %s\n", strerror(errno));
		n = strlen(buff);
		write(writefd, buff, n);
	}else{
		/*open succeeded:copy file to IPC channel*/	
		while((n = read(fd, buff, MAXLINE)) > 0)
			write(writefd, buff, n);
		close(fd);
	}

}</span>
client.c
<span style="font-size:14px;">#include "pipe.h"

int
client(int readfd, int writefd)
{
	size_t 	len;
	ssize_t	n;
	char 	buff[MAXLINE];

	/*read pathname*/
	fgets(buff, MAXLINE, stdin);
	len = strlen(buff);
	if(buff[len-1] == '\n')
		len--;

	/*write pathname to IPC channel*/
	write(writefd, buff, len);

	/*read from IPC, write to standard output*/
	while((n = read(readfd, buff, MAXLINE)) > 0)
		write(STDOUT_FILENO, buff, n);
}</span>
pipe.h
<span style="font-size:14px;">#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>

#define MAXLINE 4096</span>
编译:gcc  server.c client.c mainpipe.c -o mainpipe
可得可执行文件mainpipe。

上面给出的例程中,都是先创建管道,调用fork启动另外一个进程,然后读其输出或向其输入端发送数据,比较麻烦。为此,标准I/O库提供了两个函数popen和pclose函数。其实现的操作包括:创建一个管道,调用fork产生一个子进程,关闭管道的不适用端,执行一个shell以运行命令,然后等待命令终止。
函数原型:
#include <stdio.h>
FILE *popen(const char *cmdstring, const char *type);//返回值:若成功则返回文件指针,若出错则返回NULL
int     pclose(FILE *fp);//返回值:cmdstring的终止状态,若出错则返回-1
函数popen先执行fork,然后调用exec以执行cmdstring,并且返回一个标准I/O文件指针。
如果type是“r”,则文件指针连接到cmdstring的标准输出。
如果type是“w”,则文件指针连接到cmdstring的标准输入。
图解函数popen如下:


使用popen函数重写客户-服务器程序代码如下:
<span style="font-size:14px;">#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXLINE 4096

int
main(int argc, char **argv)
{
	size_t 		n;
	char 		buff[MAXLINE], command[MAXLINE];
	FILE 		*fp;

	/*read pathname*/
	fgets(buff, MAXLINE, stdin);
	n = strlen(buff);			/*fgets() guarantees null byte at end*/
	if(buff[n-1] == '\n')
		n--;					/*delete newline from fgets()*/

	snprintf(command, sizeof(command), "cat %s", buff);
	fp = popen(command, "r");

	/*copy from pipe to standard output*/
	while(fgets(buff, MAXLINE, fp) != NULL)
		fputs(buff, stdout);

	pclose(fp);
	exit(0);
}</span>
对比两个代码可以发现,使用popen函数要简单许多。