0. 前言
命名管道也被称为FIFO(first in first out)文件,它是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但是它的行为却和之前所讲的没有名字的管道(匿名管道)类似。
由于Linux中所有的事物都可被视为文件,所以对命名管道的使用也就变得与文件操作非常的统一,也使它的使用非常方便,同时我们也可以像平常的文件名一样在命令中使用。
特点:
- FIFO可以在无关的进程之间交换数据,与无名管道不同。
- FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
1. 原型
1.1 创建fifo
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename, mode_t mode);
返回值:若成功,返回0;若出错,返回-1
mkfifo函数中的mode 参数的规格说明与open 函数中的mode 相同。创建的fifo 文件的权限组和用户组,下面会介绍。
1.2 open()
open(const char *path, O_RDONLY);
open(const char *path, O_RDONLY | O_NONBLOCK);
open(const char *path, O_WRONLY);
open(const char *path, O_WRONLY | O_NONBLOCK);
FIFO文件使用open 来打开,非阻塞标志O_NONBLOCK会产生下列影响:
-
若没有指定
O_NONBLOCK
(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。 -
若指定了
O_NONBLOCK
,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。
2. 实例
test_fifo.h
#ifndef __TEST_FIFO_INCLUDE__
#define __TEST_FIFO_INCLUDE__
#define fifo_path "test_fifo"
#endif
test_read.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
//include 3 header files for fifo
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "test_fifo.h"
int main()
{
printf("main for reading fifo, pid is %d\n", getpid());
if (access(fifo_path, F_OK) == -1) {
printf("create fifo %s.\n", fifo_path);
if (mkfifo(fifo_path, 0644) < 0) {
printf("create fifo failed, %d.\n", errno);
exit(0);
}
}
int fd = -1;
char buff[128] = {0};
printf("open fifo for reading messages.\n");
fd = open(fifo_path, O_RDONLY); //open fifo
if (fd < 0) {
printf("open fifo failed.\n");
exit(0);
}
printf("open fifo for reading messages successfully, fd = %d\n", fd);
int buff_len = 0;
do {
buff_len = read(fd, buff, 128); //read fifo
if (buff_len > 0)
printf("%d read messages: %s\n", getpid(), buff);
} while(buff_len > 0);
close(fd); //close fifo
//unlink(fifo_path); //delete fifo file
return 0;
}
test_write.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <string.h>
//include 3 header files for fifo
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "test_fifo.h"
int main()
{
printf("main for writing fifo, pid is %d\n", getpid());
/*if (access(fifo_path, F_OK) == -1) {
printf("create fifo %s.\n", fifo_path);
if (mkfifo(fifo_path, 0644) < 0) {
printf("create fifo failed, %d.\n", errno);
exit(0);
}
}*/
int fd = -1;
char buff[128] = {0};
printf("open fifo for writing messages.\n");
fd = open(fifo_path, O_WRONLY); //open fifo
if (fd < 0) {
printf("open fifo failed.\n");
exit(0);
}
printf("open fifo for writing messages successfully, fd = %d\n",fd);
int buff_len = 0;
for (int i=0; i<10; i++) {
buff_len = sprintf(buff, "send message %d from %d", i, getpid());
if (write(fd, buff, buff_len+1) < 0) { //write fifo
printf("write fifo failed.\n");
close(fd);
exit(0);
}
sleep(1);
}
close(fd); //close fifo
return 0;
}
例子比较简单,通过test_write 写入数据,在test_read中读出。
在test_read中创建了fifo 文件:
prw-r--r-- 1 shift shift 0 4月 17 10:20 test_fifo|
文件属性第一个字母为“p”,代表是管道文件。
另外,创建FIFO之后,可能出现多个client 通过已经的FIFO给服务器发送消息,这就涉及到了PIPE_BUF,如果不希望多个进程所写的数据交叉,则必须考虑原子写操作。
在阻塞的情况下:
- 如果write的字节数小于等于PIPE_BUF,那么write会阻塞到写入所有数据,并且 写入操作是原子的。
- 如果write的字节数大于PIPE_BUF,那么write会阻塞到写入所有数据,但写入操作不是原子的,即write会根据当前缓冲区剩余的大小,写入相应的字节数,然后等待下一次有空余的缓冲区,这中间可能会有其他进程进行write操作。
在非阻塞的情况下:
- 如果write的字节数小于等于PIPE_BUF,且管道或FIFO有足以存放要写入数据大小的空间,那么就写入所有数据;
- 如果write的字节数小于等于PIPE_BUF,且管道或FIFO没有足够存放要写入数据大小的空间,那么就会立即返回EAGAIN错误。
- 如果write的字节数大于PIPE_BUF,且管道或FIFO有至少1B的空间,那么就内核就会写入相应的字节数,然后返回已写入的字节数;
- 如果write的字节数大于PIPE_BUF,且管道或FIFO无任何的空间,那么就会立即返回EAGAIN错误。
详细的PIPE_BUF,可以通过ulimit -a 或者 man 7 pipe 来查看。
相关博文: