linux 进程间通信之FIFO

1.概述

FIFO与管道几乎类似,所以FIFO也是一个字节流,从FIFO读取的顺序也是与被写入FIFO的顺序一致,容量是也有限的,也是可以确保写入不超过PIPE_BUF字节的操作是原子的,FIFO的本质也是一个管道,但传递方向是可以双向的,它们两者之间的最大差别在于FIFO在文件系统中拥有一个名称,并且打开方式与打开一个普通文件是一样的(使用open),这样就能够将FIFO用于非相关进程之间的通信(如客户端和服务器)。(不熟悉管道的可以看我的另一篇文章讲述管道linux 进程间通信之管道

2.创建FIFO

#include<sys/stat.h>
int mkfifo(const char *pathname,mode_t mode);//return 0 on success,or -1 on error
复制代码
  • mode 参数指定了新FIFO的权限即文件权限(rw-rw----)
  • mode 参数会与进程中的umask值进行异或来指定最终的权限数值(所以一般设置umask(0))

3.打开FIFO

  • FIFO被创建成功,任何进程都能够打开它,只要它能够通过常规的文件权限检测即最初设置的mode。
readFd=open(pathname,O_RDONLY);//打开只读方式

复制代码
writeFd=open(pathname,O_WRONLY);//打开只写方式
复制代码
  • 打开一个FIFO以便读取数据(open() O_RDONLY标记)将会阻塞直到另一个进程打开FIFO以写入数据(open() O_WRONLY)为止。相应地,打开一个FIFO以写如数据将会堵塞知道另一个进程打开FIFO以读取数据为止。

4.使用FIFO唯一明智的做法是在两端分别设置一个读取进程和一个写入进程的原因

  • 可以确保每次写入不超过PIPE_BUF字节的操作是原子的,当超过PIPE_BUF字节,内核会对消息进行拆分,那么就有可能混淆与其他写者发送的消息,如果只有一个写者则不用担心混淆即可以忽略这个限制。

  • 多个客户端从FIFO中读取数据时会相互竞争,这样就可能会出某个客户端读取到其他客户端的响应消息。

  • 在单服务器、多客户端应用程序中使用FIFO

服务端程序核心

// we get the permissions we want
    umask(0);
    if(mkfifo(SERVER_FIFO,S_IRUSR|S_IWUSR|S_IWGRP)==-1&&errno!=EEXIST){
        ERR_EXIT("mkfifo");
    }
    serveFd=open(SERVER_FIFO,O_RDONLY);
    if(serveFd==-1){
        ERR_EXIT("open");
    }

for(;;){
        //Read requests and send responses
        if (read(serveFd, &req, sizeof(struct request)) != sizeof(struct request)) {
                errMsg("ERROR reading request;discarding\n");
                continue;
        }
        //Open client FIFO (previously created by client)
        snprintf(clientFifo,CLIENT_FIFO_NAME_LEN,CLIENT_FIFO_TEMPLATE,(long)req.pid);
        clientFd=open(clientFifo,O_WRONLY);
        if(clientFd==-1){
           errMsg("open\n");
            continue;
        }
        
        //send response and close FIFO
        if(write(clientFd,&resp, sizeof(struct response))!= sizeof(struct response)){
            errMsg("Error writing to FIFO");
        }
        if(close(clientFd)==-1){
            errMsg("close");
        }
      
    }

复制代码

客户端程序核心

    //create our FIFO (before sending request,to avoid a race)
    umask(0);
    snprintf(clientFifo,CLIENT_FIFO_NAME_LEN,CLIENT_FIFO_TEMPLATE,(long)getpid());
    if(mkfifo(clientFifo,S_IRUSR|S_IWUSR|S_IWGRP)==-1&&errno!=EEXIST){
        ERR_EXIT("mkfifo");
    }
    serverFd=open(SERVER_FIFO,O_WRONLY);    
    if(serverFd==-1){
        ERR_EXIT("open");
    }   
    if (write(serverFd, &req, sizeof(struct request)) != sizeof(struct request)) {
            ERR_EXIT("write");
    }
    //open our FIFO,read and display response
    clientFd=open(clientFifo,O_RDONLY);
    if(clientFd==-1){
        ERR_EXIT("open");
    }
    if(read(clientFd,&resp, sizeof(struct response))!= sizeof(response)){
        ERR_EXIT("read");
    }
    if(close(clientFd)==-1){
        ERR_EXIT("close");
    }
复制代码

5.非阻塞I/O

当一个进程打开一个FIFO的一端时,如果FIFO的另一端还没有被打开,则该进程会被阻塞。但有些时候阻塞并不是期望的行为,可以通过调用open()时指定O_NONBLOCK

fd=open("fifopath",O_RDONLY|O_NONBLOCK);
if(fd==-1){
    errExit("open");
}
复制代码

5.1 打开一个FIFO时使用O_NONBLOCK标记存在两个目的

  • 它允许单个进程打开一个FIFO的两端。这个进程首先会在打开FIFO时指定O_NONBLOCK标记以便读取数据,接着打开FIFO以便写入数据。
  • 它防止打开两个FIFO的进程之间产生死锁。

打开两个FIFO的进程之间的死锁

5.2 非阻塞read()和write()

  • O_NONBLOCK 标记不仅会影响open()的语义,还会影响后续的read()和write()调用语义。

  • 可以通过fcntl() 启用或禁用打开着的文件的O_NONBLOCK状态的标记。

启用标记

int flags;
flags=fcntl(fd,F_GETFL);//Fetch open files status flags
flags|=O_NONBLOCK; // Enable O_NONBLOCK bit
fcntl(fd,F_SETFL,flags);// Update open files status flags
复制代码

禁用标记

flags=fcntl(fd,F_GETFL);
flags&=~O_NONBLOCK; //disable O_NONBLOCK bit
fcntl(fd,F_SETFL,flags);
复制代码

6.管道和FIFO中read和write的语义

从一个包含p字节的管道或FIFO中读取n字节的语义

向一个管道或FIFO写入n字节的语义

猜你喜欢

转载自juejin.im/post/5bc88ff35188254961598a19
今日推荐