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
有两个方法:
- 直接在命令行输入mkfifo 管道名称。
- 调用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;
}