UNIX(编程-进程间通信):05---FIFO命名管道(mkfifo、mkfifoat)

一、FIFO的特点

  • FIFO也称为有名管道、命名管道
  • 与无名管道的区别:无名管道(PIPE)只能在相关的进程之间使用,而且这两个相关的进程还要有一个共同的创建了它们的祖先进程。但是FIFO在不相关的进程之间也能够交换数据
  • FIFO是一种文件类型。通过stat结构的st_mode成员的编码可以知道文件是否为FIFO类型。也可以用S_ISFIFO宏对此进行测试
  • 重点:FIFO虽然是一个文件,但其本质是一个管道,供两个进程之间进行通信,但是不具备存储功能

二、FIFO的创建

#include 
int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);

//返回值:成功返回0;出错返回-1
  • 概念:创建FIFO类似于创建文件,可以用这两个函数创建FIFO文件

mkfifo函数

  • path为创建的FIFO文件的名称
  • mode为FIFO文件的初始访问权限,与open函数的最后一个参数相同

mkfifoat函数

  • 此函数与mkfifo函数类似,但是mkfifoat函数可以被用来在fd文件描述符表示的目录相关的位置创建一个FIFO。像其他*at函数一样,有以下三种情况

UNIX(编程-进程间通信):05---FIFO命名管道(mkfifo、mkfifoat)_第1张图片

UNIX(编程-进程间通信):05---FIFO命名管道(mkfifo、mkfifoat)_第2张图片

三、FIFO的打开使用

  • 概念:创建的FIFO文件用open来打开使用

用open函数打开FIFO文件时,是否指定O_NONBLOCK(非阻塞)参数的区别

  • 阻塞打开:没有指定O_NONBLOCK参数打开FIFO文件,只读open要阻塞到某个其他进程为写而打开这个FIFO文件时为止。同理,只写open要阻塞到某个其他进程为读而打开它为止
  • 非阻塞打开:指定O_NONBLOCK参数打开FIFO文件,只读open立即返回。但是,如果没有进程为读而打开一个FIFO,那么只写open将返回-1,并将errno设置为ENXIO

读写FIFO时的注意事项:

  • 如果write一个尚无进程为读而打开的FIFO,则产生信号SIGPIPE
  • 若某个FIFO的最后一个写进程关闭了该FIFO,则将为该FIFO的读进程产生一个文件结束标志

四、PIPE_BUF常量

  • 和管道一样,常量PIPE_BUF说明了可被原子地写到FIFO的最大数据量

五、多个进程读写FIFO

  • FIFO文件可以被多个进程写和读。因此,在多个进程读写这个FIFO时,需要考虑原子操作。但是一般,我们不会这么做

六、FIFO的2个用途

  • ①shell命令使用FIFO将数据从一条管道传送到另一条时,无需创建中间临时文件
  • ②客户进程-服务器进程应用程序中,FIFO用作聚会点,在客户进程和服务器进程进程二者之间传递数据

用途①:用FIFO复制输出流(tee命令)

使用FIFO和tee命令就可以实现对一个经过过滤的输入流进行两次处理

  • 下面演示用tee命令将其标准输入同时复制到其标准输出以及命令行中命名的文件中

UNIX(编程-进程间通信):05---FIFO命名管道(mkfifo、mkfifoat)_第3张图片

  • 创建FIFO,然后在后台启动prog3,从FIFO读数据。然后启动prog1,用tee将其输出发送到FIFO和prog2

UNIX(编程-进程间通信):05---FIFO命名管道(mkfifo、mkfifoat)_第4张图片

用途②:使用FIFO进行客户进程-服务器进程通信

UNIX(编程-进程间通信):05---FIFO命名管道(mkfifo、mkfifoat)_第5张图片

七、演示案例

  • 下面建立一个server程序和一个client程序
  • 程序的使用先启动server程序,创建一个FIFO文件,并且用open函数以只写的方式阻塞到该FIFO文件被一个写进程打开。之后再启动client程序,以只读的方式open该FIFO文件。之后server便可以输入数据,然后client程序接收数据并打印到标准输出上
//server.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define _PATH_  "./fifo.tmp"
#define MAXSIZE 1024

int main()
{
    int ret,fd,len;
    char buff[MAXSIZE];

    //access判断是否存在,如果不存在则创建,存在则不创建
    if(access(_PATH_,F_OK)==-1)
    {
        ret=mkfifo(_PATH_,0666|S_IFIFO);
        if(ret==-1){
            perror("mkfifo");
            exit(EXIT_FAILURE);
        }
    }
    
    //open函数没有指定O_NONBLOCK参数,则FIFO为阻塞读写
    if((fd=open(_PATH_,O_WRONLY|O_CREAT,0644))==-1){
        perror("open");
        exit(EXIT_FAILURE);
    }

    
    while(1){
        printf("Server:");
        fflush(stdout);
        scanf("%s",buff);
        len=strlen(buff);
        buff[len]='\0';
        //如果FIFO文件的写端没有打开,则write一直阻塞到FIFO的写端建立
        if((ret=write(fd,buff,len))<0){
            perror("write");
            break;
        }
        //如果输入quit,代表退出
        if((strncmp(buff,"quit",4))==0){
            break;
        }
    }

    close(fd);
    exit(0);
}
//client.c
#include 
#include 
#include 
#include 
#include 

#define _PATH_ "./fifo.tmp"
#define MAXSIZE 1024

int main()
{

    int fd,ret;
    char buff[MAXSIZE];

    //open函数没有指定O_NONBLOCK参数,则FIFO为阻塞读写
    if((fd=open(_PATH_,O_RDONLY|O_CREAT))==-1){
        perror("open");
        exit(EXIT_FAILURE);
    }

    while(1){
        //如果FIFO的读端都关闭了,read返回0
        //并且,如果FIFO的写端没有打开或没有数据传输过来,则read一直阻塞
        if((ret=read(fd,buff,sizeof(buff)))<=0){
            perror("read");
            break;
        }
        buff[ret]='\0';
        //如果server传送过来quit,代表退出
        if((strncmp(buff,"quit",4))==0){
            printf("server exit\n");
            break;
        }
        printf("server:%s\n",buff);
    }

    close(fd);
    exit(0);
}

UNIX(编程-进程间通信):05---FIFO命名管道(mkfifo、mkfifoat)_第6张图片

八、演示案例:使用FIFO实现shell的功能

  • 程序功能:send程序发送一条命令给recv端,recv端接收到命令,使用execve开启一个新程序,并将新程序的输入回送给send程序
//recv程序
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define _PATH_  "./send.tmp"
#define _PATH2_ "./recv.tmp"

#define MAXSIZE 1024

int max(int fd1,int fd2);
int main()
{
    int fd1,fd2,n,ret,maxfd;
    char buff[MAXSIZE];
    pid_t pid;
    char path[]="/bin/";

    if((fd1=open(_PATH_,O_RDONLY,0644))==-1){
        perror("open");
        exit(EXIT_FAILURE);
    }

    if((fd2=open(_PATH2_,O_WRONLY,0644))==-1){
        perror("open");
        exit(EXIT_FAILURE);
    }

    fd_set readFd;
    fd_set writeFd;

    maxfd=max(fd1,fd2);
    
    while(1)
    {
        FD_ZERO(&readFd);
        FD_ZERO(&writeFd);
        FD_SET(fd1,&readFd);
        FD_SET(fd2,&writeFd);
        switch(ret=select(maxfd+1,&readFd,&writeFd,NULL,NULL))
        {
            case 0:
                continue;
            case -1:
                perror("select");
                exit(EXIT_FAILURE);
            default:
                if(FD_ISSET(fd1,&readFd))
                {
                    memset(buff,'\0',sizeof(buff));
                    if((n=read(fd1,buff,sizeof(buff)))<0){
                        perror("read");
                        exit(EXIT_FAILURE);
                    }
                    printf("read:%s\n",buff);
                    if(strncmp(buff,"quit",4)==0){
                        goto end;
                    }
                        
                    if(FD_ISSET(fd2,&writeFd))
                    {
                        if((pid=fork())<0){
                            perror("fork");
                            exit(EXIT_FAILURE);
                        }else if(pid==0){
                            char *buf[]={buff,NULL};
                            char *env[]={"PATH=/bin",NULL};
                            //使用dup2使标准输出指向于fd2的文件表项,达到标准输出的重定向,这样标准输出的内容就会输出到fd2上
                            dup2(fd2,1);
                            if(execve(strncat(path,buff,strlen(buff)),buf,env)==-1){
                                perror("execve");
                                exit(EXIT_FAILURE);
                            }
                            exit(0);
                        }else{
                            wait(NULL);
                        }
                    }
                    printf("write:%s\n",buff);
                }
                break;
        }
    }
end:
    printf("quit\n");
    close(fd1);
    close(fd2);
    exit(0);
}

int max(int fd1,int fd2)
{
    if(fd1>fd2)
        return fd1;
    else if(fd1
//send程序
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define _PATH_  "./send.tmp"
#define _PATH2_ "./recv.tmp"

#define MAXSIZE 1024

int max(int fd1,int fd2);
int main()
{
    int fd,fd1,fd2,n,ret,i;
    pid_t pid;
    char buff[MAXSIZE];

    //文件不存在则创建
    if(access(_PATH_,F_OK)==-1){
        if(mkfifo(_PATH_,0666|S_IFIFO)==-1){
            perror("mkfifo");
            exit(EXIT_FAILURE);
        }
    }
    if(access(_PATH2_,F_OK)==-1){
        if(mkfifo(_PATH2_,0666|S_IFIFO)==-1){
            perror("mkfifo");
            exit(EXIT_FAILURE);
        }
    }

    if((fd1=open(_PATH_,O_WRONLY,0644))==-1){
        perror("open");
        exit(EXIT_FAILURE);
    }
    
    if((fd2=open(_PATH2_,O_RDONLY,0644))==-1){
        perror("open");
        exit(EXIT_FAILURE);
    }

    fd_set readFd;
    fd_set writeFd;
    
    while(1)
    {
        FD_ZERO(&writeFd);
        FD_SET(fd1,&writeFd);
        switch(ret=select(fd1+1,NULL,&writeFd,NULL,NULL))
        {
            case 0:
                continue;
            case -1:
                perror("select");
                exit(EXIT_FAILURE);
            default:
                if(FD_ISSET(fd1,&writeFd))
                {
                    memset(buff,'\0',sizeof(buff));
                    printf("command:");
                    fflush(stdout);
                    scanf("%s",buff);
                    if(strncmp(buff,"quit",4)==0){
                        if(write(fd1,buff,strlen(buff))==-1){
                            perror("write");
                            break;
                        }
                        goto end;
                    }
                    if(write(fd1,buff,strlen(buff))==-1){
                        perror("write");
                        break;
                    }

                    FD_ZERO(&readFd);
                    FD_SET(fd2,&readFd);
                    switch(ret=select(fd2+1,&readFd,NULL,NULL,NULL))
                    {
                        case 0:
                            continue;
                        case -1:
                            perror("select");
                            exit(EXIT_FAILURE);
                        default:
                            if(FD_ISSET(fd2,&readFd))
                            {
                                memset(buff,'\0',sizeof(buff));
                                if((n=read(fd2,buff,sizeof(buff)))<0){
                                    perror("read");
                                    break;
                                }
                                printf("%s",buff);
                            }
                    }
                }
                break;
        }
    }
end:
    printf("quit\n");
    close(fd1);
    close(fd2);
    exit(0);
}

UNIX(编程-进程间通信):05---FIFO命名管道(mkfifo、mkfifoat)_第7张图片

你可能感兴趣的:(UNIX(编程-进程间通信))