FIFO
FIFO有时被称为命名管道。管道只能由相关进程使用,但是,通过FIFO,不相关进程也能交换数据。FIFO的路径名存在于文件系统中,一般的文件I/O函数都可用于FIFO。
创建FIFO类似于创建文件:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo (const char* pathname, mode_t mode) ;
其中pathname是一个普通的Unix路径名,它是该FIFO的名字。
mode参数指定文件权限位,类似于open的第二个参数
【注意】
mkfifo已隐含指定O_CREAT|O_EXCL。即它要么创建一个新的FIFO,要么返回一个EEXIST错误。如果不希望创建一个新的FIFO,那就应调用open而不是mkfifo。
要打开一个已存在的FIFO或创建一个新的FIFO,应先调用mkfifo,检查它是否返回EEXIST错误,若返回则改为调用open。
当打开(open)一个FIFO时,非阻塞标志产生下列影响:
1、一般情况,若当前尚无任何进程打开FIFO来写,那么打开该FIFO读的进程将阻塞。同样,只写open要阻塞到某个其他进程为读而打开它。
2、如果指定了O_NONBLOCK,则只读open立即返回。但是,若当前尚无任何进程打开FIFO来读,那么只写open将出错返回-1。
(管道、FIFO都可设置为非阻塞。)
类似于管道,若用write写一个尚无进程为读而打开的FIFO,则产生信号SIGPIPE。若用read读一个已关闭的FIFO,则会读到一个文件结束标志。
【注意】写操作的原子性
一个给定的FIFO有多个写进程是很常见的。(其名字在文件系统名中,所有进程都可见)
若不希望多个进程所写数据互相穿插,则需要考虑原子写操作。常量PIPE_BUF说明了可被原子地写到FIFO的最大数据量。(进程一次向FIFO写入的数据大小不要超过PIPE_BUF)
FIFO的用途:用于客户进程--服务器进程应用程序中
FIFO的真正优势在于:服务器可以是一个长期运行的进程(例如守护进程),而且与其客户可以无亲缘关系。
作为服务器的守护进程以某个众所周知的路径名创建一个FIFO,并打开该FIFO来读。此后某个时刻启动的客户打开该FIFO来写,并将其请求通过该FIFO发送出去。(客户到服务器)
每个客户在启动时创建自己的FIFO,所用的路径名含有自己的进程ID。每个客户把自己的请求写入服务器的众所周知的FIFO中,该请求含有客户的进程ID以及一个请求文件路径名,服务器根据客户进程ID可以知道客户FIFO的路径名。
//注意:此为迭代型服务器。若有多个客户端请求,服务器会先处理完一个,再处理下一个。
//
//
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#define MAXLINE 1024
#define SERV_FIFO "./fifo.serv"
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) //在文件系统中创建的FIFO的访问权限位
int
main(int argc, char** argv)
{
int readfifo ; //从自己的FIFO读
int writefifo ; //向进程对应的FIFO写
int fd ; //文件描述符
char buff[MAXLINE] ;
char fifoname[MAXLINE] ;
char* ptr ;
ssize_t n ;
//创建服务器的FIFO
if ((mkfifo(SERV_FIFO, FILE_MODE) < 0) && (errno != EEXIST))
printf("can't create %s", SERV_FIFO) ;
//打开服务器FIFO的读、写端
readfifo = open(SERV_FIFO, O_RDONLY, 0) ;
//读取来自客户端的请求
while ((n = read(readfifo, buff, MAXLINE)) > 0)
{
if (buff[n-1] == '\n')
buff[--n] = '\0' ;
//获取客户端的进程ID号
if ((ptr = strchr(buff, ' ')) == NULL)
{
printf("bogus request: %s", buff) ; //伪造的客户请求
continue ;
}
*ptr++ = 0 ;
//根据客户端进程ID打开客户端FIFO
snprintf(fifoname, sizeof(fifoname), "./fifo.%s", buff) ;
if ((writefifo = open(fifoname, O_WRONLY, 0)) < 0)
{
printf("cannot open : %s", fifoname) ;
continue ;
}
//向客户端FIFO中写入客户所请求的文件内容
if ((fd = open(ptr, O_RDONLY)) < 0)
{ //打开文件出错,向客户端返回错误信息
snprintf(buff+n, sizeof(buff) - n, ":can't open, %s\n", strerror(errno)) ;
n = strlen(ptr) ;
write(writefifo, ptr, n) ;
close(writefifo) ;
}
while ((n = read(fd, buff, MAXLINE)) > 0)
write(writefifo, buff, n) ;
close(writefifo) ;
close(fd) ;
}//while
exit(0) ;
}
//客户端知道服务器端FIFO的名字,且服务器知道它维护的FIFO的名字是 fifo.进程ID
//
//---------------客户端-------------
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#define MAXLINE 1024
#define SERV_FIFO "./fifo.serv"
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
int
main(int argc, char** argv)
{
int readfifo ;
int writefifo ;
size_t len ;
ssize_t n ;
char* ptr ;
char fifoname[MAXLINE] ;
char buff[MAXLINE] ;
pid_t pid ;
//创建自己的FIFO 用于接收服务器端的数据
pid = getpid() ;
snprintf(fifoname, sizeof(fifoname), "./fifo.%ld", (long)pid) ;
if (mkfifo(fifoname, FILE_MODE) < 0 && (errno != EEXIST))
printf("can't create %s", fifoname) ;
//构造请求消息
snprintf(buff, sizeof(buff), "%ld ", (long)pid) ;
len = strlen(buff) ;
ptr = buff + len ;
fgets(ptr, MAXLINE - len, stdin) ; //从键盘获取请求文件路径
len = strlen(buff) ;
//打开服务器的FIFO 向其中写入消息
writefifo = open(SERV_FIFO, O_WRONLY, 0) ;
write(writefifo, buff, len) ;
//打开自己的FIFO 读取来自服务器的应答数据
readfifo = open(fifoname, O_RDONLY, 0) ;
while ((n = read(readfifo, buff, MAXLINE)) > 0)
write(STDOUT_FILENO, buff, n) ;
close(readfifo) ;
unlink(fifoname) ;
exit(0) ;
}
注意,此程序有一些不足:服务器进程不能判断一个客户进程是否崩溃终止,这就使得客户进程专用的FIFO会遗留在文件系统中。
此为迭代式服务器,另一种设计是并发服务器。即每个客户一个子进程服务器。每当有一个客户请求到达时,这种服务器就让主进程调用fork派生出一个新的子进程。该新子进程处理相应的客户请求,直到完成为止。
管道和FIFO的特征之一是它们的数据是一个字节流。这是UNIX的原生I/O模型。进程往其中写入的是字节流,系统不对它作解释。如果需要某种解释,写进程和读进程就得先验地同意这种解释,并亲自去做。(即我们可以人为地定义规则,对字节流解释)