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

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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值