管道作为最古老的进程间通信方法,它有以下几个特点:
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
#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函数要简单许多。