管道作为最古老的进程间通信方法,它有以下几个特点:
1、在所有的UNIX实现中都存在。
2、没有名字,因此只能由有亲缘关系的进程使用。
3、它由函数pipe创建,read和write函数访问,但只提供单路(单向)数据流。
#include <unistd.h>
int pipe(int fd[2]); 返回:若成功则为0,若出错则为-1
经由参数fd返回两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。fd[1]的输出是fd[0]的输入。
宏S_ISFIFO可用于确定一个描述符或文件是管道还是FIFO。它的唯一参数是stat结构的st_mode成员。对于管道来说,这个stat结构是由fstat函数填写的。对于FIFO来说,这个结构是由fstat、lstat或stat函数填写的。
单进程使用管道的情况一般如图所示:
图 单个进程中的管道
尽管管道是由单个进程创建的,它却很少在单个进程内使用。通常,调用pipe的进程接着调用fork,这样就创建了从父进程到子进程或反向的IPC通道。如图所示;
图 单个进程内的管道,刚刚fork后
对于从父进程到子进程的管道,父进程关闭管道的读端fd[0],子进程关闭写端fd[1]。结果如图所示:
图 从父进程到子进程的管道
从父进程到子进程的管道,并且父进程经由该管道向子进程传送数据。程序示例如下:
#include <unistd.h>
#define MAXLINE 4096
int
main(void)
{
int n;
int fd[2];
pid_t pid;
char line[MAXLINE];
if(pipe(fd) < 0){
printf("pipe error");
return -1;
}
if((pid = fork()) < 0){
printf("fork error");
return -1;
}else if(pid > 0){ /*parent 父进程*/
close(fd[0]);
write(fd[1], "hello world", 12);
}else{ /*child 子进程*/
close(fd[1]);
n = read(fd[0], line, MAXLINE);
write(STDOUT_FILENO, line, n);
}
exit(0);
}
编译: gcc pipe.c -o pipe
测试:./pipe
结果:hello world
上面所说的管道都是半双工的即单向的,只提供一个方向的数据流。当需要一个双向数据流时,必须创建两个管道,每个方向一个。实际步骤如下:
1、创建管道1和管道2
2、fork
3、父进程关闭管道1的读出端、关闭管道2的写入端
3、子进程关闭管道1的写入端、关闭管道2的读出端。
完成上述步骤后的管道布局如下图所示。
#include "pipe.h"
extern void client(int, int);
extern void server(int, int);
int
main(int argc, char *argv[])
{
int pipe1[2], pipe2[2];
pid_t pid;
pipe(pipe1);
pipe(pipe2);
if((pid = fork()) < 0){
printf("fork error.\n");
return -1;
}else if(pid == 0){ /*child*/
close(pipe1[1]);
close(pipe2[0]);
server(pipe1[0], pipe2[1]);
exit(0);
}else{ /*parent*/
close(pipe1[0]);
close(pipe2[1]);
client(pipe2[0], pipe1[1]);
waitpid(pid, NULL, 0); /*wait for child to terminate*/
exit(0);
}
}
server.c
#include "pipe.h"
void
server(int readfd, int writefd)
{
int fd;
ssize_t n;
char buff[MAXLINE+1];
/*read pathname frome IPC channel*/
if((n = read(readfd, buff, MAXLINE)) == 0){
printf("end-of-file while reading pathname.\n");
return ;
}
buff[n] = '\0'; /*null terminate pathname*/
if((fd = open(buff, O_RDONLY)) < 0){
/*error:must tell client*/
snprintf(buff+n, sizeof(buff)-n, ":can't open, %s\n", strerror(errno));
n = strlen(buff);
write(writefd, buff, n);
}else{
/*open succeeded:copy file to IPC channel*/
while((n = read(fd, buff, MAXLINE)) > 0)
write(writefd, buff, n);
close(fd);
}
}
client.c
#include "pipe.h"
int
client(int readfd, int writefd)
{
size_t len;
ssize_t n;
char buff[MAXLINE];
/*read pathname*/
fgets(buff, MAXLINE, stdin);
len = strlen(buff);
if(buff[len-1] == '\n')
len--;
/*write pathname to IPC channel*/
write(writefd, buff, len);
/*read from IPC, write to standard output*/
while((n = read(readfd, buff, MAXLINE)) > 0)
write(STDOUT_FILENO, buff, n);
}
pipe.h
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#define MAXLINE 4096
编译:gcc server.c client.c mainpipe.c -o mainpipe可得可执行文件mainpipe。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXLINE 4096
int
main(int argc, char **argv)
{
size_t n;
char buff[MAXLINE], command[MAXLINE];
FILE *fp;
/*read pathname*/
fgets(buff, MAXLINE, stdin);
n = strlen(buff); /*fgets() guarantees null byte at end*/
if(buff[n-1] == '\n')
n--; /*delete newline from fgets()*/
snprintf(command, sizeof(command), "cat %s", buff);
fp = popen(command, "r");
/*copy from pipe to standard output*/
while(fgets(buff, MAXLINE, fp) != NULL)
fputs(buff, stdout);
pclose(fp);
exit(0);
}
对比两个代码可以发现,使用popen函数要简单许多。