Posix管道基本知识

来源:互联网 发布:淘宝客网页模板 编辑:程序博客网 时间:2024/05/18 08:04

1. 基本信息

管道是最早的IPC形式,一般可以分为管道和FIFO(命名管道)两类,使用通常的read和write进行读写。管道可以分为全双工和半双工管道。不同版本的Unix提供了不同的实现。SVR4的pipe创建的为全双工管道,而posix1标准则为半双工管道。一般是先全双工管道使用socketpair函数较为可靠。

2. pipe、popen、pclose

#include <unistd.h>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 <stdio.h>FILE *popen(const char *command, const char *type);//成功返回文件指针,失败返回NULLint pclose(FILE *stream);//成功返回shell中止状态,出错返回-1
command      shell命令行,函数在调用进程和命令行进程间创建一个管道

type                 管道类型,type = w为写管道、type = r 为读管道


3. 命名管道(FIFO)

上面我们提到的主要是匿名管道的一些接口,其主要是针对有亲缘关系的进程通信。而命名管道可以允许无亲缘关系的进程访问同一个FIFO。

#include  <sys/type.h>#include <sys/stat.h>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);}

4. 额外属性

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可查,和管道路径相关。


0 0
原创粉丝点击