Linux_ mkfifo 命名管道 操作
管道的缺点
管道只能在具有“亲戚”关系的进程之间通信。
即仅当管道由某个进程创建之后,在该进程的所有子孙进程之间,可通过该管道来通信。其他情况下的无此“亲戚”关系的进程不能使用管道通信。
解决办法:使用命名管道
什么是命名管道?
命名管道是一种特殊的文件,
命名管道以普通文件的形式(在文件系统中有一个确定的路径和文件名)存在。
任意进程只要使用该文件就能通信。注意:无名管道(即管道),是通过文件描述符的形式使用。
命名管道,是通过该命令管道文件的“文件名”来使用。命名管道的创建
1) 使用mkfifo
用法: man 3 mkfifo
原型: int mkfifo(const char *pathname, mode_t mode);
参数: mode,类似于open的第三个参数,表示该管道的访问权限。
pathname, 表示该命名管道的绝对路径。
返回值:成功,返回0
失败,返回-1
实例: main1.c查看已创建的管道 (1) ls -l /tmp/myfifo 第一个字符为p, 表示该文件是一个管道 (2) ls -F /tmp/myfifo 最后一个字符为|, 表示该文件是一个管道
2) 使用mknod
用法: man 2 mknod
原型:int mknod(const char *pathname, mode_t mode, dev_t dev);
参数:mode, 一定要含有 | S_IFIFO
注:该API能用来创建各种特殊文件。
dev, 取0
实例:main2.c
返回值:成功,返回0
失败,返回-1命令管道的使用
1) 通过shell命令使用
假设已有命名管道“/tmp/fifo”,(1) 从命名管道读数据,如果没有数据,则将被阻塞 # cat /tmp/myfifo (2) 在另一个终端上,向该命名管道发送数据 # echo hello > /tmp/myfifo (3) 第(1)中的终端,将读取到数据。
2) 通过open使用命名管道
(1) open命名管道与open普通文件之间的区别:区别1: open命名管道时,不能以O_RDWR方式打开,只能以只读或只写方式打开。 因为命名管道是单向的。 如果以读写方式打开,结果将不可预期。 如果要使用命名管道进行双向数据传输,只能: a) 创建两个命名管道,分别以不同方向打开。 b) 只创建一个命名管道, 先使用一个方向进行open,传递数据之后,再关闭。 然后再用反方向方式打开,进行反方向传输。 区别2: open命名管道时,O_NONBLOCK的意义与普通文件不同。 区别3: open命名管道时,具有同步作用 (2) open命名管道的合法方式: a) 只读阻塞式open open(MYFIFO, O_RDONLY); 效果:open操作将被阻塞,直到其他进程以写方式打开该FIFO b) 只写阻塞式open write(MYFIFO, O_WRONLY); 效果:open操作将被阻塞,直到其他进程以读方式打开该FIFO c) 只读非阻塞式open open(MYFIFO, O_RDONLY | O_NONBLOCK); 效果:马上成功返回,无论其他进程是否以写方式打开该FIFO d) 只写非阻塞式open open(MYFIFO, O_WRONLY | O_NONBLOCK); 效果:马上返回,无论其他进程是否以读方式打开该FIFO 如果其他进程没有以读方式打开该FIFO, 则返回-1,该FIFO打开失败。 如果其他进程已经以读方式打开该FIFO, 则返回一个文件描述符,打开成功。 注意:对于FIFO, O_NONBLOCK对于只读和只写有不同的意义! 对于普通文件,O_NONBLOCK对于读和写的含义相同。
多进程使用FIFO时的常用组合方式
多进程在打开FIFO时,能够获得“同步”。1) 进程A以只读阻塞式打开FIFO
进程B以只写阻塞式打开FIFO
最常用。2) 进程A以只读非阻塞式打开FIFO
进程B以只写阻塞式打开FIFO3) 其他组合方式,不常用。
实例:main3_r.c
main3_w.c
在不同终端上运行。
命名管道的读写
1)读命名管道
如果FIFO是以阻塞式打开的(即没有使用O_NONBLOCK)
read时,如果当前该FIFO中没有数据,则阻塞read, 直到FIFO中被写入数据。
如果当前该FIFO中有数据,则读取数据后返回。如果FIFO是以非阻塞式打开的(即使用O_NONBLOCK) read时,如果当前该FIFO中没有数据,则马上返回0。 如果当前该FIFO中有数据,则读取数据后返回。 如果关闭了FIFO的写端文件描述符,则read直接返回0(无论是否是阻塞式read) 该特性和两端口的管道相同。
2) 写命名管道
如果FIFO是以阻塞式打开的(即没有使用O_NONBLOCK)
write时,如果当前该FIFO不能再写入数据时, 则阻塞write, 直到FIFO可被写入数据。
如果当前该FIFO还能写入数据,则写数据后返回。注:该方式下,如果要写入的数据长度 <= PIPE_BUF, 则要么全部写入,要么都不写入。 如果FIFO是以非阻塞式打开的(即使用O_NONBLOCK) write时候,如果该FIFO当前还能容纳要写入的全部数据,则写入全部数据后返回。 如果该FIFO当前已不能容纳要写入的全部数据,则 如果要写入的数据长度 > PIPE_BUF , 则写入部分数据,然后返回。 返回值为实际写入的字节数,也可能返回0 如果要写入的数据长度 <= PIPE_BUF , 则不写入任何数据,然后返回。 如果关闭了FIFO的读端文件描述符,则write可能产生异常,而直接终止程序
3) 写FIFO时的“原子性”
如果写操作都是阻塞式的,而且每次要写入的数据长度 <= PIPE_BUF,
那么,各进程的对该FIFO的写操作不会”交错”, 即都是原子性的。其他情况,可能发生“交错”。
所以,每次要写入的字节数最好设置为PIPE_BUF。
注意:PIPE_BUF在limits.h中定义使用命名管道实现IPC
注意:使用FIFO,进行进程间通信时存在“两级”同步:
打开时,同步。
读写时,同步。实例:main4.c
进程A向进程B传送10M数据。
并测试进程B读取10M数据所耗时间。练习:main5.c
进程A循环等待用户输入,
用户每输入一个单词后,就把该单词用管道发送给进程B,
直到用户输入exit。进程B每收到一个单词后,就统计该单词的长度并打印输出
直到进程A结束输入。
使用命令管道来创建服务器和客户端
代码:
版本1:客户端关闭时,服务器不关闭
day5/server_client/
版本2:客户端关闭时,服务器也关闭。
day5/server.c
day5/client.c