1. 基本信息
管道是最早的IPC形式,一般可以分为管道和FIFO(命名管道)两类,使用通常的read和write进行读写。管道可以分为全双工和半双工管道。不同版本的Unix提供了不同的实现。SVR4的pipe创建的为全双工管道,而posix1标准则为半双工管道。一般是先全双工管道使用socketpair函数较为可靠。
2. pipe、popen、pclose
#include
int pipe(int fd[2]);
该函数会返回两个文件描述符,fd[0]用来读、fd[1]用来写。
尽管管道由单个进程创建,但其一般会作为进程通信的手段。过程大致如下:
首先,一个进程创建管道后fork出一个自身的副本
接着,父进程关闭读入端,子进程关闭写入端
这样就形成了一条父进程到子进程的单向流。实际上当我们执行who | sort | lp命令时,将创建3个进程和2个管道。而如果我们需要双向数据流时,其步骤如下:
A. 创建两条管道(fd1[2] 和 fd2[2])
B. fork出自身的副本
C. 父进程关闭管道1的读和管道2的写
D. 子进程关闭管道1的写和管道2的读
#include
FILE *popen(const char *command, const char *type);//成功返回文件指针,失败返回NULL
int pclose(FILE *stream);//成功返回shell中止状态,出错返回-1
command shell命令行,函数在调用进程和命令行进程间创建一个管道
type 管道类型,type = w为写管道、type = r 为读管道
3. 命名管道(FIFO)
上面我们提到的主要是匿名管道的一些接口,其主要是针对有亲缘关系的进程通信。而命名管道可以允许无亲缘关系的进程访问同一个FIFO。
#include
#include
int mkfifo(const char *pathname, mode_t mode);
pathname 普通Unix路径名,也是该FIFO的名字
mode 权限位
本函数已经隐含指定了O_CREAT | O_EXCL标志位,即其要么创建一个FIFO,要么返回一个EEXIST错误,如果我们不希望创建一个新的FIFO则,我们需要使用open函数。在shell环境下我们可以使用mkfifo命令创建命名管道。实际上FIFO不能打开既读又写,有且仅可以由一个。对FIFO的写操作总是将内容添加到末尾,读操作总是从开头返回数据。下面我们给出一个无亲缘关系的服务器-客户端示例子:
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifl.2"
void server(int ,int );
int main(int argc, char *argv[]){
int readfd,writefd;
if((mkfifo(FIFO1,FILE_MODE) < 0) && (errno != EEXIST))
perror("can't create");
if((mkfifo(FIFO2,FILE_MODE) < 0) && (errno != EEXIST)){
unlink(FIFO1);
perror("can't cread");
}
readfd = open(FIFO1, O_RDONLY, 0);
writefd = open(FIFO2, O_WRONLY, 0);
server(readfd,writefd);
}
客户端:
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifl.2"
void server(int ,int );
int main(int argc, char *argv[]){
int readfd,writefd;
readfd = open(FIFO2, O_RDONLY, 0);
writefd = open(FIFO1, O_WRONLY, 0);
client(readfd,writefd);
close(readfd);
close(writefd);
unlink(FIFO1);
unlink(FIFO2);
exit(0);
}
A. open管道时,可以指定O_NONBLOCK;或使用fcntl指定O_NONBLOCK.
B. read时,管道中的数据小于read要求的数据时,只返回可用数据
C. write时,要写入的数据小于等于PIPE_BUF,则保证写操作是原子性的,否则不保证原子性
D. write时,如果管道为非阻塞,若写入字节大于管道可用字节,则返回EAGAIN(小于PIPE_BUF);若FIFO中有一个字节的空间,则返回写入的字节数,如果满,则返回EAGAIN(大于PIPE_BUF);
E. 如果向一个没有为读打开的管道写入,则会产生一个SIGPIPE.默认为中止进程,若忽略该信号,则write返回EPIPE.
D. read一个没有为写打开的管道时,read会返回0(文件结束符)。当面对多客户的情形时,一旦最后一个客户退出,就会返回文件结束符,我们不得不关闭描述符然后重新打开,等待下一个读打开。在设计时,我们可以再服务器进程中打开一个写管道,这样就可以保证管道的写端永远有一个以上的写打开。
5. 字节流
实际上,管道使用字节流I/O模型,其不区分记录边界,解决的方法有三个:
A. 带内特殊终止符:写操作加入换行符,读每次读一行
B. 显式长度:每个记录前冠以它的长度
C. 每次连接一个记录:以连接断开标志为记录结束标志,需要为每个记录创建连接
D. fdopen:标准IO库也能读写一个管道,我们可以通过fdopen将标准IO与pipe返回的描述符相关联,可以使用fprintf, fscanf等标准库函数(结构化读写)。
E. 我们还可以自己构建结构化的消息,每个消息一个记录,这样通过读写结构化的消息就可以了,这种方案示例如下:
#define MAXMESDATA (PIPE_BUF - 2 * sizeof(long))
#define MESGHDRSIZE (sizeof(struct mymesg) - MAXMESDATA)
struct mymesg{
long mesg_len;
long mesg_type;
char mesg_data[MAXMESDATA];
};
ssize_t mesg_send(int fd, struct mymesg *mptr){
return write(fd,mptr,MESGHDRSIZE+mptr->mesg_len);
}
ssize_t mesg_recv(int fd, struct mymesg *mptr){
size_t len;
ssize_t n;
if((n = read(fd,mptr,MESGHDRSIZE)) == 0)
return 0;
else if( n != MESGHDRSIZE)
return -1;
if(( len = mptr->mesg_len) > )
if((n = read(fd,mptr->mesg_data,len)) != len)
return -1;
return len;
}
OPEN_MAX 进程最大打开文件描述符限制,sysconf可查
PIPE_BUF 可以原子化的读写一个管道的最大数据量pathconf或fpathconf可查,和管道路径相关。