主题:Linux管道知识
概要:进程间通信,管道相关的知识
编辑:新建 20151024
参考资料:
Linux程序设计,第四版,人民邮电出版社
1 什么是管道 ?
当从一个进程连接数据流到另一个进程时,使用术语管道,表述把一个进程的输出通过管道连接到另一个进程的输入。
2 Popen调用
2.1 函数定义
popen和pclose函数是最简单的在两个程序之间传递数据的函数,原型如下:
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
popen函数允许一个程序将另一个程序作为新进程来启动,并可以传递数据给它或者给它传递接收数据。
command:要调用程序的程序名和相应的参数
open_mode:”r”或”w”,”r”是指调用程序读取被调用程序的输出为输入;”w”是指调用程序能向被调用程序写入数据。
pclose:关闭调用程序与被调用程序之间的数据流。
例1:
void Popen1()
{
FILE * read_fp;
char buffer[BUFSIZ+1];
int chars_read;
memset(buffer,'\0',sizeof(buffer));
read_fp=popen("uname -a","r");
if (read_fp!=NULL)
{
chars_read=fread(buffer,sizeof(char),BUFSIZ,read_fp);
if (chars_read>0)
{
printf("Out put is:%s \n",buffer);
printf("Total len is:%d\n",chars_read);
}
pclose(read_fp);
}
}
popen函数调用一个程序时,会先启动一个shell,然后将command字符串作为参数传给它。这样做一方面,好处是,通过popen能够启动非常复杂的shell命令,不必自己去完成shell扩展。坏处是,对每个popen调用,不仅要启动一个被请求的程序,还要启动一个shell,调用成本略高。
3 PIPE调用
3.1 函数定义
pipe函数提供了通过两个文件描述符在两个程序之间传递数据的功能,函数原型如下:
#include<unistd.h>
int pipe(int filedes[2])
参数为两个整数类型文件描述符组成的数组,该函数在数组中填上两个新的文件描述符后返回值0 ,如果失败返回1并设置errno来表明失败的原因。两个文件描述符以一种特殊的方式连接起来,写到filedes[1]的数据都可以从filedes[0]读出来。数据根据先进先出的顺序进行处理。
例2:
一个简单的写入-读出程序:
void Pipe1()
{
int date_processed;
int fife_pipes[2];
const char * some_data="who are you";
char buffer[BUFSIZ+1];
memset(buffer,'\0',sizeof(buffer));
if (pipe(fife_pipes)==0)
{
date_processed=write(fife_pipes[1],some_data,strlen(some_data));
printf("Wrote %d bytes\n",date_processed);
date_processed=read(fife_pipes[0],buffer,BUFSIZ);
printf("Read %d bytes,is :%s \n",date_processed,buffer);
}
}
例3:
结合fork调用,通过pipe函数能轻易的进行两个进程间的数据传递。
//--通过pipe进行父子程序间通信
void Pipe2()
{
int date_processed;
int fife_pipes[2];
const char * some_data="who are you";
char buffer[BUFSIZ+1];
memset(buffer,'\0',sizeof(buffer));
pid_t fork_result;
if (pipe(fife_pipes)==0)
{
fork_result=fork();
if (fork_result==-1)
{
fprintf(stderr,"fork error");
}
else if (fork_result==0)
{
date_processed=read(fife_pipes[0],buffer,BUFSIZ);
printf("Read from [%d],total %d bytes,is :%s \n ",getpid(),date_processed,buffer);
}
else
{
date_processed=write(fife_pipes[1],some_data,strlen(some_data));
printf("Wrote to [%d],total %d bytes\n",getpid(),date_processed);
}
}
}
父进程向管道中写数据,而子进程从管道中读取数据,如图所示:
例4:
fork函数只能在父子进行间通信,而通过调用exec可进行两个完全不同的程序之间的通信。
写-生产者程序:
void Pipe3()
{
int date_processed;
int fife_pipes[2];
const char * some_data="who are you";
char buffer[BUFSIZ+1];
memset(buffer,'\0',sizeof(buffer));
pid_t fork_result;
if (pipe(fife_pipes)==0)
{
fork_result=fork();
if (fork_result==-1)
{
fprintf(stderr,"fork error");
}
else if (fork_result==0)
{
sprintf(buffer,"%d",fife_pipes[0]);
execl("PipeClient","PipeClient",buffer,(char *) 0);
}
else
{
date_processed=write(fife_pipes[1],some_data,strlen(some_data));
printf("Wrote to [%d],total %d bytes\n",getpid(),date_processed);
}
}
}
读-消费者程序 (PipeClient.c)
#include <unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include <string.h>
int main(int argc,char * argv[])
{
int data_processed;
char buffer[BUFSIZ+1];
int file_descriptor;
memset(buffer,0,sizeof(buffer));
sscanf(argv[1],"%d",&file_descriptor);
data_processed=read(file_descriptor,buffer,BUFSIZ);
printf("%d -read %d bytes :%s/n",getpid(),data_processed,buffer);
exit(EXIT_SUCCESS);
}
生产者程序子进程中通过execl函数调用了PipeClient进程,把读文件描述符作为参数传递给被调用进程,消费者程序通过传给它的读描述符读取数据。
4 FIFO命名管道
前面的进程通信都由一个共同的进程祖先启动,而想在两个完全不相关的进程间通信还是不很方便,可以用FIFO文件来完成这一功作,通常也被称为命名管道。命名管道是一种特殊类型的文件,在文件系统中以文件名的形式存在,行为与上面没有名字的管道类似。
函数原型:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo( const char *pathname, mode_t mode );
第一个参数(pathname)是将要在文件系统中创建的一个专用文件。第二个参数(mode)用来规定FIFO的读写权限。Mkfifo函数如果调用成功的话,返回值为0;如果调用失败返回值为-1。
例4:
创建一个命名管道
void Fifo1()
{
int res=mkfifo("/home/book/chapter13/my_fifo",0777);
if (0==res)
{
printf("FIFO created \n");
}else
{
printf("FIFO created error \n");
}
exit(EXIT_SUCCESS);
}
mkfifo函数创建一个特殊的文件,文件类型为p,文件模式由参数mode和用户掩码(umask)设置共同确定。
4.1 使用open打开FIFO文件
open函数的原型为:
#include <fcntl.h>
int open(const char *pathname, int oflag, ... );
oflag 用于指定文件的打开/创建模式。
使用O_RDONLY模式:
open(const char *pathname, O_RDONLY );
open调用将阻塞,除非一个进程以写方式打开同一个FIFO,否则它不会返回。
使用O_RDONLY|O_NONBLOCK 模式:
open(const char *pathname, O_RDONLY |O_NONBLOCK );
即使没有其他进程以写方式打开FIFO,这个调用也能成功返回。
使用O_WDONLY模式:
open(const char *pathname, O_WDONLY );
open调用将阻塞,除非一个进程以读方式打开同一个FIFO,否则它不会返回。
使用O_WDONLY|O_NONBLOCK 模式:
open(const char *pathname, O_WDONLY |O_NONBLOCK );
这个函数调用总是立刻返回,但如果没有进程以读方式打开FIFO,open调用将返回一个错误并且FIFO也不会被打开。如果确实有一个进程以读方式打开FIFO,那么可以通过它返回的文件描述符对这个FIFO文件进行写操作。
例5:
创建一个生产者程序,它在需要时创建管道,并尽可能快的向管道中写入数据。
void Fifo3()
{
int pipe_fd;
int res;
int open_mode=O_WRONLY;
int bytes_sent=0;
char buffer[BUFSIZ+1];
int end_tag=65535;
if(access(FIFO_NAME,F_OK)==-1)
{
res=mkfifo(FIFO_NAME,0777);
if (res!=0)
{
fprintf(stderr,"Created fifo error");
exit(EXIT_FAILURE);
}
}
printf("Prosess [%d] open fifo\n",getpid());
pipe_fd=open(FIFO_NAME,open_mode);
printf("Prosess [%d] open fifo result [%d]\n",getpid(),pipe_fd);
if (pipe_fd!=-1)
{
while(bytes_sent<end_tag)
{
res=write(pipe_fd,buffer,BUFSIZ);
if (res==-1)
{
fprintf(stderr,"Write error on pipe\n");
exit(EXIT_FAILURE);
}
bytes_sent+=res;
}
close(pipe_fd);
}
else
{
exit(EXIT_FAILURE);
}
printf("Prosess [%d] finished\n",getpid());
exit(EXIT_SUCCESS);
}
创建消费者模式,它从FIFO中读取数据并抛弃它们。
#include <unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include <string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define FIFO_NAME "/home/book/chapter13/my_fifo"
int main(int argc,char * argv[])
{
int pipe_fd;
int res;
int open_mode=O_RDONLY;
int bytes_read=0;
char buffer[BUFSIZ+1];
memset(buffer,0,sizeof(buffer));
printf("Prosess [%d] open fifo\n",getpid());
pipe_fd=open(FIFO_NAME,open_mode);
printf("Prosess [%d] open fifo result [%d]\n",getpid(),pipe_fd);
if (pipe_fd!=-1)
{
do
{
res=read(pipe_fd,buffer,BUFSIZ);
bytes_read+=res;
} while (res>0);
close(pipe_fd);
}
else
{
exit(EXIT_FAILURE);
}
printf("Prosess [%d] finished,read [%d] bytes\n",getpid(),bytes_read);
exit(EXIT_SUCCESS);
}
作为演示,上面的各函数均可以在main函数里直接调用,如:
#include <unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include <string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define FIFO_NAME "/home/book/chapter13/my_fifo"
int main()
{
Fifo1();
return 0;
}