Posix管道基本知识

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);
}

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


你可能感兴趣的:(基础知识)