简介
管道是UNIX系统最古老的IPC形式。它有以下两点局限性:
- 数据只能单向流动
- 只能在具有公共祖先的两个进程间使用。
通常是父进程通过系统调用pipe()创建管道,然后fork()出子进程,这两个进程就可以通过管道进行通信了。
术语定义
管道fd:一条管道有两个文件描述符,这两个文件描述符是在调用pipe(fd)创建管道时获取获取的。
- fd[0]:通过该文件描述符可以从管道中读取数据
- fd[1]:通过该文件描述符可以向管道写入数据
写端进程:只向管道写数据的进程。在使用管道时,先close(fd[0])、再通过write(fd[1])向管道写数据。
读端进程:只从管道读数据的进程。在使用管道时,先close(fd[1])、在通过read(fd[0])从管道读取数据。
代码示例
下列代码在linux环境下,粘贴到本地后可以直接编译运行。
#include <unistd.h> //declare int pipe(int pipefd[2]);
#include <limits.h> //PIPE_BUF
#include <stdio.h>
#include <sys/wait.h>
#include <string.h>
#define MSG_FROM_PARENT "Hello, world!"
int fd[2] = {0}; //存储管道的两个文件描述符,fd[0] for read, fd[1] for write
int main(int argc, char **argv)
{
int pid = 0;
/* 打印一下PIPE_BUF的值,这个值定义在linux/limits.h中 */
// #define PIPE_BUF 4096 /* # bytes in atomic write to a pipe */
printf("PIPE_BUF:[%d]\n", PIPE_BUF);
if (0 != pipe(fd))
{
perror("pipe");
return -1;
}
pid = fork();
if (pid < 0) // fork error
{
perror("fork");
return -1;
}
else if (pid > 0) // branch of parent
{
close(fd[0]); // parent close read
/* write to pipe */
if (write(fd[1], MSG_FROM_PARENT, sizeof(MSG_FROM_PARENT)) < 0)
{
perror("write");
close(fd[1]);
return -1;
}
/* !!非常重要,writer写完后必须关闭write end
* 否则reader在读完管道中的数据后,再次读管道时会因为没有数据而一直阻塞 */
close(fd[1]); //建议注释掉这行代码,运行查看下效果。有惊喜哟。你会发现printf()中\n有将缓冲区刷新的效果
/* 父进程阻塞等待子进程退出 */
if (waitpid(pid, NULL, 0) < 0)
{
perror("waitpid");
return -1;
}
printf("parent exit\n");
}
else // branch of son
{
close(fd[1]);
char read_buf[5] = {0}; // 故意把buf设的小一点,这样可以多次读取管道,观察更多细节
int readlen = 0;
do{
memset(read_buf, 0, sizeof(read_buf));
perror("before read");
readlen = read(fd[0], read_buf, sizeof(read_buf) - 1);
if (readlen < 0)
{
perror("read");
close(fd[0]);
return -1;
}
perror("after read");
printf("%s", read_buf); // printf()的fmt如果加上'\n',其效果可不只是换行哟。
//'\n'其实还有fflush(stdout)的作用:将printf缓冲区的数据刷新到stdout(即屏幕)上。
}while (readlen > 0);
close(fd[0]);
printf("\nson exit\n");
}
return 0;
}
重点总结
如果pipe的一端被关闭 ,以下两条规则将生效:
- 如果写端关闭,那么读端将管道中的数据全部读完后,如果再次调用read,将会返回0。
换言之,如果写端进程写完数据后没有关闭pipe的写端文件描述符,那么读端进程在读取一个不再有数据可读的管道时会阻塞。 - 如果读端关闭,那么写端write时,将会产生信号SIGPIPE,如果该信号未被处理,write将返回-1,且errno被置为EPIPE。
关于linux信号处理,有时间单写一篇博客总结一下。这里只需要注意,向一个读端关闭的管道写数据,write会返回-1;如果读端没有关闭,但是管道的数据一直没有被读端进程读走,那么当管道中缓存数据达到PIPE_BUF规定的上限时(本示例中打印出的上限是4096 bytes),write会阻塞!
以上两条规则告诉我们,不管是读端进程还是写端进程,用完了管道都要记得关闭啊!否则对端进程再次读写管道时会阻塞的!好严重的!
=============
先写到这里,其实还有一些需要总结,有时间再填坑吧。