进程间通信笔记(2)—管道和FIFO

来源:互联网 发布:java ringbuffer 编辑:程序博客网 时间:2024/05/16 01:27

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 mypipexxx@xxx: echo helloworld > mypipe

在另一终端:

xxx@xxx: read line < mypipexxx@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

1 0
原创粉丝点击