1. 概述
管道没有名字,适用于有亲缘关系的进程间。
FIFO指first in first out,有一个路径名与之关联,从而允许无亲缘关系的进程间使用。亦称:命名管道named pipe。
两者都是单向数据流(半双工管道),具有随进程的持续性,数据都是先进先出,在进程间通信不需要某种形式的同步。
2.管道
#include <unistd.h>
int pipe(int fd[2]);
返回:成功0,出错-1;
返回两个文件描述符:fd[0]打开来读,fd[1]打开来写。
典型使用:
(1) 单向的
在父子进程间提供通信手段。
父进程创建管道,然后fork出自身的副本。
父进程关闭读端,用于向管道中写。
子进程关闭写端,用于从管道中读。
(2) 双向的
pipe(pipe1[2]); pipe1[0], pipe1[1]
pipe(pipe2[2]); pipe2[0], pipe2[1]
fork() == 0 {//子进程
close(pipe1[1]);
close(pipe2[0]);
}
//父进程
close(pipe1[0]);
close(pipe2[1]);
3.pipe例子
下面讲述一个客户——服务器程序。
客户发送路径名,服务器发送文件内容给客户。
#include "unp.h" void client(int, int), server(int, int); int main(void) { int pipe1[2], pipe2[2]; //创建两个管道 //也会在子进程中创建 Pipe(pipe1); Pipe(pipe2); int childid = Fork(); if (childid == 0) { //child Close(pipe1[1]); //关闭写 Close(pipe2[0]); //关闭读 server(pipe1[0], pipe2[1]); exit(0); } Close(pipe1[0]); Close(pipe2[1]); client(pipe2[0], pipe1[1]); //等待子进程终止 waitpid(childid, NULL, 0); exit(0); }
server.c代码:
#include "unp.h" //从管道中读取文件名 //通过管道把文件发送给客户端 void server(int readfd, int writefd) { char buff[MAXLINE + 1]; ssize_t n = Read(readfd, buff, MAXLINE); if (n == 0) { err_quit("end-of-file while reading pathname"); } buff[n] = ‘\0‘; int fd = open(buff, O_RDONLY); if (fd < 0) { snprintf(buff + n, sizeof(buff) - n, ": can‘t open, %s\n", strerror(errno)); n = strlen(buff); Write(writefd, buff, n); } else { while ((n = Read(fd, buff, MAXLINE)) > 0) { Write(writefd, buff, n); } Close(fd); } }
client.c源码:
#include "unp.h" //从标准输入读入文件路径名, //通过管道发送给服务器, //在从管道接受文件的内容写到标准输出 void client(int readfd, int writefd) { char buff[MAXLINE]; Fgets(buff, MAXLINE, stdin); size_t len = strlen(buff); if (buff[len - 1] == ‘\n‘) { --len; } Write(writefd, buff, len); ssize_t n; while((n = Read(readfd, buff, MAXLINE)) > 0) { Write(STDOUT_FILENO, buff, n); } }
4.FIFO
与管道最大差别是有名字,适用于无亲缘进程间。
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
mkfilo隐含指定O_CREAT | O_EXCL, 要是路径已存在返回EEXIST。
若不想创建一个新的FIFO,可以调用open。
mode类似于open的第二个参数,默认定义FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)。
5.无亲缘关系的客户与服务器
就是上面的客户端发送文件名,服务器端读取文件内容给客户端的在两个独立进程中的写法。
服务器端源码:
#include "unp.h" void server(int, int); //服务端创建命名两个管道, //在一个上open用于读,在另一个上open用于写 int main(void) { //创建时默认O_CREAT | O_EXCL //要检测路径是否已经存在,即EEXIST if ((mkfifo("/tmp/fifo.1", FILE_MODE)) && (errno != EEXIST)) { err_sys("can‘t create /tmp/fifo.1"); } if ((mkfifo("/tmp/fifo.2", FILE_MODE)) && (errno != EEXIST)) { //从文件系统中删除路径,也就时FIFO的名字 //文件的链接计数减1, //等到为0时,且没有进程在使用文件时,删除文件 unlink("tmp/fifo.1"); err_sys("can‘t create /tmp/fifo.2"); } int readfd = Open("/tmp/fifo.1", O_RDONLY, 0); int writefd = Open("/tmp/fifo.2", O_WRONLY, 0); server(readfd, writefd); exit(0); }
客户端源码:
#include "unp.h" void client(int, int); //打开服务器端已经创建好的管道, //一个用于读,一个用于写,与服务器端相反 int main(void) { int writefd = Open("/tmp/fifo.1", O_WRONLY, 0); int readfd = Open("/tmp/fifo.2", O_RDONLY, 0); client(readfd, writefd); Close(readfd); Close(writefd); //由客户端删除管道 Unlink("/tmp/fifo.1"); Unlink("/tmp/fifo.2"); exit(0); }
server.c 和 client.c参见上面。
注意:
使用FIFO时,必须注意open的顺序。
在没有进程打开FIFO来写,那么打开该FIFO来读的进程将阻塞。
所以在打开FIFO时必须时在一个上读在另一个上写,不能同时打开读然后写。
即open(读);open(写);
open(读);open(写);
会造成死锁,都阻塞在读上。
6. 管道和FIFO额外属性
参见UNPv2 P44,4.7节。
7.一些特性
(1) 向FIFO中write是原子性的操作,一次性写入的东西作为一个整体来看待。
(2) FIFO只能在单台主机上使用的IPC形式。不能用在NFS安装的文件系统中。
(3) 管道是字节流,系统不对它进行解释。
进程间通信(一)——管道和FIFO,布布扣,bubuko.com