1.概述
管道(pipe):局限在于没有名字,只能用于亲缘关系的进程使用。
FIFO:称为有名管道(named pipe)
2.客户-服务器例子
跟套接字编程的套路类似,客户-服务器回射程序:
这里客户从标准输入(stdin)读入一个路径名,并把它写入IPC通道。服务器打开文件,读出其中内容,并写入IPC通道作为对客户的响应;客户将服务器回射来的内容打印即写到标准输出(stdout)。
3.管道
#include <unistd.h>
int pipe(int fd[2]);
pipe
函数创建管道,并提供单向的数据流(不是全双工哦,如果需要双向数据流则需要创建两个管道),该函数返回两个文件描述符:fd[0]
和fd[1]
。前者用来读,后者用来写。
管道的典型应用是用于父子进程的通信,父进程创建一个管道后调用fork派生出自己的副本,接着父进程关闭管道的读出端,子进程关闭写入端。这样父子进程间就有了一个单向的数据流,如下示意:
另外,在shell中使用“|”作为管道命令符,例如:
cat /etc/issue | grep Ubuntu
在两个进程之间创建了一个管道,通过管道,前一个进程的标准输出传递给下一个进程作为标准输入。
3.1示例
下面实现一个经典的管道示例,父进程和子进程之间完成通信,在主程序中创建两个管道,用于双向通信。父进程为客户,子进程为服务器,第一个管道用于客户向服务器发送路径名,第二个管道用于服务器向客户发送该文件内容:
3.2代码
通过创建两个管道实现全双工的通信:父进程从标准输入读入文件的路径名并写入管道,子进程从管道中读出文件名并打开文件,读出文件中的内容写入管道,父进程从管道中接收文件中的内容并写入到标准输出。
为了方便学习,也是把书中的代码解包裹。。。
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
void server(int ,int);
void client(int ,int);
const int MAXLINE = 1024;
int main(int argc,char ** argv)
{
int pipe1[2];
int pipe2[2];
pid_t childpid;
//创建两个管道
if(pipe(pipe1)<0)
printf("pipe1 error\r\n");
if(pipe(pipe2)<0)
printf("pipe2 error\r\n");
if((childpid = fork())==0) //子进程
{
close(pipe1[1]);//关闭写
close(pipe2[0]);//关闭读
server(pipe1[0],pipe2[1]);
return 0;
}
//父进程
close(pipe1[0]);//关闭读
close(pipe2[1]);//关闭写
client(pipe2[0],pipe1[1]);
wait(NULL);
return 0;
}
void client(int readfd,int writefd)
{
size_t len;
ssize_t n;
char buff[MAXLINE];
//读入路径名
if(fgets(buff,MAXLINE,stdin)==NULL)
{
printf("fgets error\r\n");
return ;
}
len=strlen(buff);
if(buff[len-1]=='\n')
len--;
//路径名写入管道
if(write(writefd,buff,len)<0)
{
printf("write error\r\n");
return ;
}
//接收文件中数据,写入到标准输出
while((n=read(readfd,buff,MAXLINE))>0)
write(STDOUT_FILENO,buff,n);//STDOUT_FILENO 1 // STDOUT_FILENO=fileno(stdout);
}
void server(int readfd,int writefd)
{
int fd;
ssize_t n;
char buff[MAXLINE];
if((n=read(readfd,buff,MAXLINE))<=0)
{
printf("read error\r\n");
return ;
}
buff[n]='\0';
fd=open(buff,O_RDONLY);
if(fd < 0)
{
snprintf(buff+n,sizeof(buff)-n,": can't open, %s\r\n",strerror(errno));
n=strlen(buff);
write(writefd,buff,n);
}//open error 错误信息返回
else
{
//从文件中读入数据并写入管道
while((n=read(fd,buff,MAXLINE))>0)
{
write(writefd,buff,n);
}
close(fd);
}
}
4.FIFO
FIFO类似于管道,它是一个单向数据流,不同的是,FIFO有一个路径名与之关联,从而允许无亲缘关系的进程访问同一个FIFO,FIFO也被称为有名管道(named pipe),使用mkfifo
函数可以创建。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char * pathname,mode_t mode);
//成功返回0 出错返回-1
不仅仅是函数,我们可以在shell下使用mkfifo创建有名管道。
例如:
xxx@xxx: mkfifo mypipe
xxx@xxx: echo helloworld > mypipe
在另一终端:
xxx@xxx: read line < mypipe
xxx@xxx: echo $line
使用fifo让两个无亲缘关系的进程进行通信,由于每个fifo有一个路径名与之关联,因此创建fifo后,需要使用I/O函数打开读或者打开写。
NOTE:fifo不能打开来既读又写,因为它是半双工的。也就是说O_RDWR这种打开模式将是未定义的
示例代码
在使用mkfifo
函数的时候,需要注意一些问题:
1.mkfifo是隐含O_CREAT | O_EXCL也就是说,该函数要么创建一个FIFO(成功),要么返回一个EEXIST错误(失败,该FIFO已经存在),对于后者来说,它并不妨碍我们继续使用这个已经存在FIFO进行通信。
2.关于创建的FIFO(所关联的那个文件),需要说明读写权限,书中给了一个默认权限:允许用户读写、组内成员和其他用户读,使用掩码的方式包括起来就是:
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
下面就可以实现一个简单的生产者消费者了,简单起见,就只创建一个FIFO完成半双工的通信了。
生产者
//fifowrite.c
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char** argv)
{
const char * const PATHNAME = "/home/zhangxiao/zxtest/pipe/myfifo";//关联的路径名
const char * const BUFF = "Fifo Write Test.\r\n";
int fd;
if( (mkfifo(PATHNAME,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)<0) && (errno != EEXIST) )
{
printf("can't create %s\r\n",PATHNAME);
return -1;
}
fd = open(PATHNAME,O_WRONLY, 0);
write(fd,BUFF,strlen(BUFF));
return 0;
}
消费者
//fiforead.c
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char** argv)
{
const int MAXLEN=1024;
const char * const PATHNAME = "/home/zhangxiao/zxtest/pipe/myfifo";//关联的路径名
ssize_t n;
int fd;
char readbuff[MAXLEN];
memset(readbuff,0x00,sizeof(readbuff));//初始化
if( (mkfifo(PATHNAME,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)<0) && (errno != EEXIST) )
{
printf("can't create %s\r\n",PATHNAME);
return -1;
}
fd = open(PATHNAME,O_RDONLY, 0);
while( (n = read(fd,readbuff,sizeof(readbuff)))>0 )
{
write(STDOUT_FILENO,readbuff,n);//标准输出
}
return 0;
}
5.参考
1.《UNP卷2》
2.http://stackoverflow.com/questions/25900873/write-and-read-from-a-fifo-from-two-different-script