UNIX/Linux进程间通信IPC系列(三)FIFO

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的路径名。


UNIX/Linux进程间通信IPC系列(三)FIFO_第1张图片


//注意:此为迭代型服务器。若有多个客户端请求,服务器会先处理完一个,再处理下一个。
//
//
#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模型。进程往其中写入的是字节流,系统不对它作解释。如果需要某种解释,写进程和读进程就得先验地同意这种解释,并亲自去做。(即我们可以人为地定义规则,对字节流解释)

 


 

你可能感兴趣的:(UNIX/Linux进程间通信IPC系列(三)FIFO)