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模型。进程往其中写入的是字节流,系统不对它作解释。如果需要某种解释,写进程和读进程就得先验地同意这种解释,并亲自去做。(即我们可以人为地定义规则,对字节流解释)