进程间通信之FIFO

FIFO俗名叫 命名管道(有名管道),是进程间通信的一种方法。其实,所谓的管道,就是内核里面的一块缓存。从管道的一段写入的数据,实际上是缓存在内核中的,另一端读取,也就是从内核中读取这段数据。另外,管道传输的数据是无格式的流且大小受限。第二个参数为管道权限,一般设置为0666就可以了,即管道文件有读®写(w)权限,不需要执行(x)权限。
区别于pipe(匿名管道),在pipe中,我们可以使用 fork 创建子进程,创建的子进程会复制父进程的文件描述符,这样就做到了两个进程各有两个fd[0](读端) 与 fd[1](写端),两个进程就可以通过各自的 fd 写入和读取同一个管道文件实现跨进程通信了。管道只能一端写入,另一端读出,所以上面这种模式容易造成混乱,因为父进程和子进程都可以同时写入,也都可以读出。为了避免这种情况,所以通常的做法是:
父进程关闭读取的 fd[0],只保留写入的 fd[1];
子进程关闭写入的 fd[1],只保留读取的 fd[0];
然后父进程写,子进程读。
但是,pipe由于需要fork创建进程,所以它只能用在父子进程间的通信,无法在不相关的进程间通信,因此有了FIFO。

对于命名管道FIFO,它可以在不相关的进程间也能相互通信。因为命令管道,提前创建了一个类型为管道的设备文件,在进程里只要使用这个设备文件,就可以相互通信。大致示意图如下

有一点需要强调:

不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,不支持 lseek 之类的文件定位操作。

FIFO的阻塞与非阻塞

使用open函数打开fifo时,第二个参数有一个非阻塞标志(O_NONBLOCK),有以下几种情况需要注意:“”
1、不指定O_NONBLOCK时:
① open以只读方式打开FIFO时,要阻塞到某个“写”进程启动时才会运行。
②open以只写方式打开FIFO时,要阻塞到某个"读"进程启动时才会运行。
③open以只读、只写方式打开FIFO时会阻塞,调用read函数从FIFO里读数据时read也会阻塞。
④调用write函数向FIFO里写数据,当缓冲区已满时write也会阻塞。

2、指定O_NONBLOCK时
①以只读方式(O_RDONLY | O_NONBLOCK)打开fifo,即便没有"写"进程启动,open也不会阻塞。
②以只写方式(O_WRONLY | O_NONBLOCK)打开fifo,如果没有‘’读”进程启动,open会出错:open: No such device or address。
③read、write读写管道中的数据时不会阻塞。

下面看一下操作系统提供的操作有名管道的一些API:
① 创建fifo
有两个方法:

  1. 直接在命令行输入mkfifo 管道名称
  2. 调用mkfifo函数进行创建。
       #include <sys/types.h>
       #include <sys/stat.h>

       int mkfifo(const char *pathname, mode_t mode);
       第一个参数为管道的路径
       第二个参数为管道的权限,一般设置为0666就可以了,即管道文件有读(r)(w)权限,没有执行(x)权限。 

如下图创建一个fifo
下面看一个简单的例子,写两个函数,一个用于往管道中写数据,另一个往管道中读数据,实现不同进程间的通信。

进程1(写数据)

#include <iostream>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <chrono>
#include <sys/types.h>
#include <sys/stat.h>
#include <thread>
using namespace std;


int main(int argc, const char* argv[]){
    if(argc < 2){ 
        cout << "参数为管道名称" << endl;
        exit(-1);
    }   
    int fd = open(argv[1], O_RDONLY);
    
    char buf[20];
    int ret = 1;
    while((ret = read(fd, buf, sizeof(buf))) > 0){ 
        if(strcmp(buf, "") != 0){ 
            cout << "读取到: " << buf;
        }   
        memset(buf, 0, sizeof(buf));
    }   
    cout << "数据已被读取完毕, 进程退出!" << endl;
    return 0;
}                  

进程2(写数据)

#include <iostream>
#include <string.h>                                                 
#include <unistd.h>
#include <fcntl.h>
#include <chrono>
#include <sys/types.h>
#include <sys/stat.h>
#include <thread>
using namespace std;

int main(int argc, const char* argv[2]){
    int fd = open(argv[1], O_WRONLY);
    if(argc < 2){
        cout << "需要一个参数: 管道名称" << endl;
        exit(-1);    
    }
    if(fd < 0){
        perror("open");
    }

    char buf[100];
    int i = 1;
    while(i < 50){
        memset(buf, 0, sizeof(buf));
        sprintf(buf, "第%d条数据\n", i++);
        cout << "写入: " << buf;
        write(fd, buf, sizeof(buf));
        this_thread::sleep_for(chrono::milliseconds(3);
    }
    close(fd);
    return 0;                                                                
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值