Chinaunix首页 | 论坛 | 博客
  • 博客访问: 11433
  • 博文数量: 2
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 14
  • 用 户 组: 普通用户
  • 注册时间: 2014-10-21 14:08
文章分类
文章存档

2016年(2)

我的朋友
最近访客

分类: LINUX

2016-10-11 13:28:32

命名管道-FIFO

1, 管道最大的劣势就是没有名字,只能用于有一个共同祖先进程的各个进程之间。FIFO代表先进先出,单它是一个单向数据流,也就是半双工,和管道不同的是:每个FIFO都有一个路径与之关联,从而允许无亲缘关系的进程访问。
2, 由于命名管道是以文件的形式创建的,所有也具有文件一些特性,它可以使用fcntl函数改变属性.
例如,要把本来是阻塞的命名管道设置成非阻塞的,需要这样:

int flags;

if ((flags = fcntl(fd, F_GETFL, 0)) < 0)
    err_exit("F_GETFL error");
flags |= O_NONBLOCK;   // 把打开的fifo管道的属性改成非阻塞的
if (fcntl(fd, F_SETFL, flags) < 0)
    err_exit("F_SETFL error");
若仅仅调用F_SETFL命令,可能清除了其他的标志属性. 例如:
if (fcntl(fd, F_SETFL, flags) < 0)
    err_exit("F_SETFL error");

3, 创建命名管道:
       #include
       #include

       int mkfifo(const char *pathname, mode_t mode);
这里pathname是路径名,mode是sys/stat.h里面定义的创建文件的权限.

例1: 有亲缘关系进程间的fifo的例子

/*
 * 有亲缘关系的进程间的fifo的使用
 * fifo 使用的简单例子
 */


#include "../all.h"

#define FIFO_PATH "/tmp/hover_fifo"


void
do_sig(int signo)
{
    if (signo == SIGCHLD)
        while (waitpid(-1, NULL, WNOHANG) > 0)
            ;
}


int
main(void)
{
    int ret;
    int fdr, fdw;
    pid_t pid;

    char words[10] = "123456789";
    char buf[10] = {'\0'};    
    
    // 创建它,若存在则不算是错误,
    // 若想修改其属性需要先打开得到fd,然后用fcntl来获取属性,然后设置属性.

    if (((ret = mkfifo(FIFO_PATH, FILE_MODE)) == -1)

                     && (errno != EEXIST))
        perr_exit("mkfifo()");
    fprintf(stderr, "fifo : %s created successfully!\n", FIFO_PATH);

    signal(SIGCHLD, do_sig);

    pid = fork();
    if (pid == 0) { // child

        if ((fdr = open(FIFO_PATH, O_WRONLY)) < 0) // 打开fifo用来写
            perr_exit("open()");
        sleep(2);

        // 写入数据
        if (write(fdr, words, sizeof(words)) != sizeof(words))
            perr_exit("write");
        fprintf(stderr, "child write : %s\n", words);
        close(fdw);
    } else if (pid > 0) { // parent

        if ((fdr = open(FIFO_PATH, O_RDONLY)) < 0) // 打开fifo用来读

            perr_exit("open()");

        fprintf(stderr, "I father read, waiting for child ...\n");
        if (read(fdr, buf, 9) != 9) //读数据
            perr_exit("read");

        fprintf(stderr, "father get buf : %s\n", buf);
        close(fdr);
    }
    // 到这里fifo管道并没有被删除,必须手动调用函数unlink或remove删除.

    return 0;    
}


从例子上可以看出使用fifo时需要注意:
*fifo管道是先调用mkfifo创建,然后再用open打开得到fd来使用.
*在打开fifo时要注意,它是半双工的的,一般不能使用O_RDWR打开,而只能用只读或只写打开.

4, fifo可以用在非亲缘关系的进程间,而它的真正用途是在服务器和客户端之间. 由于它是半双工的所以,如果要进行客户端和服务器双方的通信的话,每个方向都必须建立两个管道,一个用于读,一个用于写.

下面是一个服务器,对多个客户端的fifo的例子:

server 端的例子:



/*
 * FIFO server
 */


#include "all.h"

int
main(void)
{
    int fdw, fdw2;
    int fdr;
    char clt_path[PATH_LEN] = {'\0'};
    char buf[MAX_LINE] = {'\0'};
    char *p;
    int n;
    
    if (mkfifo(FIFO_SVR, FILE_MODE) == -1 && errno != EEXIST)    
        perr_exit("mkfifo()");    
    if ((fdr = open(FIFO_SVR, O_RDONLY)) < 0)    
        perr_exit("open()");
    /*
     * 根据fifo的创建规则, 若从一个空管道或fifo读,

     * 而在读之前管道或fifo有打开来写的操作, 那么读操作将会阻塞
     * 直到管道或fifo不打开来读, 或管道或fifo中有数据为止.

     *

     * 这里,我们的fifo本来是打开用来读的,但是为了,read不返回0,

     * 让每次client端读完都阻塞在fifo上,我们又打开一次来读.
     * 见unpv2 charper 4.7
     */

    if ((fdw2 = open(FIFO_SVR, O_WRONLY)) < 0)    
        fprintf(stderr, "open()");
    
    while (1) {
        /* read client fifo path from FIFO_SVR */

     /* 这里由于FIFO_SVR有打开来写的操作,所以当管道没有数据时,

      * read会阻塞,而不是返回0.

      */
        if (read(fdr, clt_path, PATH_LEN) < 0) {
            fprintf(stderr, "read fifo client path error : %s\n", strerror(errno));    
            break;
        }
        if ((p = strstr(clt_path, "\r\n")) == NULL) {
            fprintf(stderr, "clt_path error: %s\n", clt_path);
            break;
        }
        *p = '\0';
        DBG("clt_path", clt_path);
        if (access(clt_path, W_OK) == -1) { // client fifo ok, but no permission

            perror("access()");    
            continue;
        }
        /* open client fifo for write */
        if ((fdw = open(clt_path, O_WRONLY)) < 0) {
            perror("open()");    
            continue;
        }
        if ((n = read(fdr, buf, WORDS_LEN)) > 0) { /* read server words is ok */
            printf("server read words : %s\n", buf);
            buf[n] = '\0';
            write(fdw, buf, strlen(buf));    
        }
    }
    
    close(fdw);    
    unlink(FIFO_SVR);
    exit(0);
}


客户端的例子:


/*
 * Fifo client
 *
 */

#include "all.h"



int
main(void)
{
    int fdr, fdw;
    pid_t pid;    
    char clt_path[PATH_LEN] = {'\0'};
    char buf[MAX_LINE] = {'\0'};
    char buf_path[MAX_LINE] = {'\0'};
    
    snprintf(clt_path, PATH_LEN, FIFO_CLT_FMT, (long)getpid());        
    DBG("clt_path1 = ", clt_path);
    snprintf(buf_path, PATH_LEN, "%s\r\n", clt_path);

    if (mkfifo(clt_path, FILE_MODE) == -1 && errno != EEXIST)    
        perr_exit("mkfifo()");

    /* client open clt_path for read
     * open server for write
       */

    if ((fdw = open(FIFO_SVR, O_WRONLY)) < 0)
        perr_exit("open()");
    
    /* write my fifo path to server */    
    if (write(fdw, buf_path, PATH_LEN) != PATH_LEN)        
        perr_exit("write()");
    if (write(fdw, WORDS, WORDS_LEN) < 0)    /* write words to fifo server */
        perr_exit("error");

    if ((fdr = open(clt_path, O_RDONLY)) < 0)    
        perr_exit("open()");
    if (read(fdr, buf, WORDS_LEN) > 0) {     /* read reply from fifo server */
        buf[WORDS_LEN] = '\0';
        printf("server said : %s\n", buf);
    }
    
    close(fdr);
    unlink(clt_path);
    
    exit(0);
}



5,fifo 的读写规则
   管道和fifo的读写有很多规则,了解这些规则对使用管道是必须的.
(1) 若请求的数据多于管道或fifo的当前可用数据量,那么只返回这些可用的数据,我们必须准备好处理来自read的小于所请求数据的返回.
(2) 若请求写入的数据的字节数小于或等于PIPE_BUF,那么可以保证write的原子性.也就是说若有多个客户端向服务器端写数据,多个客户端的数据不会交叉. 这个限制每个系统不同,后面分析源码的时候会看到.
(3) O_NOBLOCK 标志对read操作的影响
 a, 没有设置O_NOBLOCK时, 对打开fifo操作的影响
 a.1 若fifo已经以读打开, 现在再以写打开,将会立即返回;若再打开来读,将会阻塞,直到再以写打开.
 a.2 若fifo已经以写打开, 现在再以读打开,将会立即返回; 若再打开来写,将会阻塞,直到再以读打开.

 b, 没有设置O_NOBLOCK时, 对打开fifo的read操作的影响

 c, 没有设置O_NOBLOCK时, 对打开fifo的write操作的影响

(待续...)

阅读(781) | 评论(0) | 转发(0) |
0

上一篇:Linux Netlink通信机制详解(上)

下一篇:没有了

给主人留下些什么吧!~~