博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
命名管道
阅读量:4589 次
发布时间:2019-06-09

本文共 4468 字,大约阅读时间需要 14 分钟。

命名管道(FIFO)

简介

  • 管道没有名字,并且随着进程存在消失.所以我们没办法通过管道使两个无亲缘关系的进程通信.
  • FIFO指的是first in first out.同管道一样,它也是一个半双工数据流.不同的是,每一个FIFO有一个路径名与之关联.
    从这个特点上我们可以看到,我们可以通过一个路径名使没有亲缘关系的进程通过FIFO来进行通信.
  • 我们通过mkfifo创建FIFO,下面我们给出mkfifo的函数原型
#include
#include
int mkfifo(const char *pathname, mode_t mode); //成功则返回0,失败则返回-1 /* mode常量值: S_IRUSR 用户(属主)读 S_IWUSR 用户(属主)写 S_IRGRP (属)组成员读 S_IWGRP (属)组成员写 S_IROTH 其他用户读 S_IWOTH 其他用户写 */
  • FIFO通过open或者fopen等标准IO打开.同时FIFO不能打开既读又写,因为它是半双工的.
  • FIFO的write总是往末尾添加数据,read则从开头返回数据.不能使用lseek.

例子

要求:进程a读取路径名,进程b获得路径名并读取文件内容并发送给进程a

//进程a#include "fifo_conf.h"#include 
#include
#include
#include
#include
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)void server(int,int);int main(int argc, char const *argv[]) { int readfd,writefd; if((mkfifo(FIFO_S, FILE_MODE) < 0) && (errno != EEXIST)){ fprintf(stderr, "Can't make a new FIFO at %s\n",FIFO_S); } if((mkfifo(FIFO_C, FILE_MODE)) < 0) && (errno != EEXIST){ unlink(FIFO_S); fprintf(stderr, "Can't make a new client FIFO at %s\n",FIFO_C); } readfd = open(FIFO_S, O_RDONLY, 0); writefd = open(FIFO_C, O_WRONLY, 0) server(readfd, writefd); return 0;}void server(int readfd, int writefd) { char pathname[PATH_MAX+1]; char buf[PIPE_BUF]; printf("Please enter the path: \n"); scanf("%s",pathname); write(writefd, pathname, strlen(pathname)); while((read(readfd, buf, PIPE_BUF)) != 0) printf("%s\n", buf); unlink(FIFO_S);}//进程b#include "fifo_conf.h"#include
#include
#include
#include
#include
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)void client(int, int);int main(int argc, char const *argv[]) { int readfd,writefd; if((mkfifo(FIFO_C, FILE_MODE)) < 0 && errno != EEXIST){ fprintf(stderr, "Can't make a new FIFO at %s\n", FIFO_C); } readfd = open(FIFO_C, O_RDONLY, 0); if((writefd = open(FIFO_S, O_WRONLY, 0)) < 0){ unlink(FIFO_C); fprintf(stderr, "Can't open the server FIFO at %s\n", FIFO_S); } client(readfd, writefd); return 0;}void client(int readfd, int writefd){ int fd,n; char buf[PIPE_BUF]; char pathname[PATH_MAX+1]; if((n = read(readfd, pathname, PATH_MAX)) == 0){ fprintf(stderr, "Can't read path from FIFO at %s\n", FIFO_C); unlink(FIFO_C); exit(0); } pathname[n] = '\0'; if((fd = open(pathname, O_RDONLY)) < 0){ fprintf(stderr, "Can't open file at %s\n", pathname); unlink(FIFO_C); exit(0); } while((n = read(fd, buf, PIPE_BUF)) != 0){ write(writefd, buf, n); } unlink(FIFO_C);}

例子解析

  • 可以看到的是,FIFO和pipe的区别主要集中在两个方面.
    • 创建方式的不同:pipe通过pipe()创建并返回两个描述符.而FIFO通过mkfifo创建并返回该描述符
    • 参数不同:pipe传入的是用来存放描述符的数组,FIFO传入的是路径以及文件权限模式.
  • 我们在调用mkfifo的时候,如果该路径已经存在,那么则会有一个EEXIST错误.
  • 可以说FIFO是带有一些文件性质的,因为我们同样也需要指定打开方式(读or写).
  • 可以看到,我们最后在Client中调用unlink函数删除两个FIFO.

FIFO与管道相关内容

  • 内核为管道和FIFO维护了一个访问计数器,它的值是访问同一个管道或者FIFO的打开着的描述符数量,听起来很像GC中的引用计数器.

    即使我们调用了unlink删除了这个FIFO也不会对已经打开的描述符造成影响.通过查阅资料印证了我的猜测,即FIFO只是借助文件系统,起到
    一个索引的作用.而真实的数据其实仍旧在内核中.所以我们删除了该文件但不会对数据造成影响.

  • FIFO中write的原子性.因为FIFO的特点,所以它也经常被多个进程同时使用.为了避免造成这样的情况,就需要在write层面提供原子性操作

    以避免乱序的问题.在FIFO中,只要写入的buf长度是小于PIPE_BUF的,那么操作系统就可以保证这是一个原子行为.

  • FIFO的阻塞问题.open函数在打开FIFO管道的时候在某些情况下是阻塞的.首先我们来看一段代码
int fd;    if((mkfifo(FIFO_C, FILE_MODE)) < 0 && errno != EEXIST){        fprintf(stderr, "Can't make a new FIFO at %s\n", FIFO_C);    }    printf("TEST TOP\n");    fd = open(FIFO_C, O_WRONLY, 0);    printf("TEST BOTTOM\n");

在本例中,"TEST BOTTOM"不会被输出.因为open函数一直被阻塞住没有返回.

那么FIFO在什么情况下会阻塞呢?
- 首先我们要明确概念,阻塞是一个双向的行为,进程a阻塞的目的是等待进程b的某些行为触发的信号.
- 在没有设置阻塞位(下文有提及)的时候,FIFO必须是双向打开的,也就是说进程a如果以读的形式打开FIFO,此时open操作会被阻塞住.直到进程b以写的形式打开FIFO,此时进程a会被唤醒然后
继续执行下去.再强调一次,阻塞的FIFO必须双向打开,否则就会造成死锁.
- 如果一端关闭了管道,而另一端向管道中写或者读都会出现问题.read返回0(文件结束符),write给线程产生SIGPIPE信号(默认行为是终止进程).

  • 阻塞给我们的程序带来了许多不便,此时我们应该怎么办呢?
    • 首先我们可以在open时通过加入O_NONBLOCK标志位(open(file, O_RDONLY | O_NONBLOCK))来设定操作均为非阻塞行为.
    • 非阻塞模式的FIFO行为模式发生了改变.当我们以只读方式打开FIFO时,描述符均会成功返回.当我们以只写方式打开FIFO时,如果FIFO没有读端则会返回一个ENXIO错误而不会阻塞.当我们read一个空FIFO时,
      如果此时FIFO有写端则会返回EAGAIN,如果没有则返回0.当我们write一个FIFO时,没有读端则产生一个SIGPIPE信号.
    • O_NONBLOCK不影响write的原子性.

字节流

FIFO和管道类似于TCP,都是使用的标准的Stream IO模式.也就是说它们并没有"包"这个概念,进程A写入100字节和进程A写入50字节,进程B写入50字节在表现形式上来看是相同的.

如果我们需要边界这个概念的话,那么需要在应用程序层面是手动添加.常见的方法如下:

  • 特殊分割字符: 例如 /r/n
  • 显式长度: 每个记录前冠以它的长度

转载于:https://www.cnblogs.com/xinglong/articles/6532570.html

你可能感兴趣的文章
Web开发小贴士 -- 全面了解Cookie
查看>>
收藏Javascript中常用的55个经典技巧
查看>>
Arm-linux-gcc-4.3.2安装步骤
查看>>
Java多线程与并发编程学习
查看>>
Support Vector Machine
查看>>
牛客-2018多校算法第五场C-KMP
查看>>
Linux查看文件内容
查看>>
[转]社会生活中十二大著名法则 1 马太效应 2 手表定理 3 不值得定律 4 彼得原理 5 零和游戏原理 6 华盛顿合作规律 7 酒与污水定律 8 水桶定律 9 蘑菇管理 10 奥...
查看>>
浅谈三层与实体
查看>>
cocostudio——js 3 final控件事件
查看>>
Flex 学习笔记 datatip的背景颜色
查看>>
iOS开发中六种手势识别
查看>>
oracle创建临时表没有权限
查看>>
76.数塔问题
查看>>
PHP 透明水印生成代码
查看>>
我就是学习
查看>>
Hibernate的缓存
查看>>
(十五)Struts2 本地化/国际化(i18n)
查看>>
利用Qt Phonon框架制作音视频播放器
查看>>
Bundle savedInstanceState的作用
查看>>