管道:属于 Linux 古老的IPC 进程间通信方式,本质是内核开辟的一块缓冲区,充当进程之间数据传输的通道,分为无名管道(pipe)、有名管道(FIFO)两种。
一、无名管道
无名管道: 只能用在具有亲缘关系的进程间通信(父子进程)

1. 本质
管道本质是内核中的一段缓冲区,默认64K字节,遵循队列存储方式(FIFO,先进先出)
2. 特点
(1)写阻塞:当管道的读写端都存在时,管道写满数据时,发生写阻塞
(2)读阻塞:当管道的读写端都存在时,管道为空时,读管道发生读阻塞
(3)读返回0:当管道没有写端,只有读端时,管道中有数据则读出数据,无数据时,不再读阻塞,读管道直接返回0字节
(4)管道破裂:当管道没有读端,向管道中写数据,发生管道破裂异常
3. 操作流程
(1)创建并打开管道:pipe();
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建并打开一个无名管道
参数:
pipefd:管道读端和写端的文件描述符
pipefd[0]:读 pipefd[1]:写
返回值:
成功:0
失败:-1
(2)读管道:read();
(3)写管道:write();
(4)关闭管道:close();
示例:
int main(void)
{
int pipefd[2];
int ret = pipe(pipefd);
if(-1 == ret)
{
perror("pipe error");
return -1;
}
pid_t pid = fork();
if(pid > 0)
{
close(pipefd[0]);//关闭读端
write(pipefd[1], "hello son", 9);
close(pipefd[1]);
}
if(0 == pid)
{
close(pipefd[1]);//关闭写端
char buff[1024] = {0};
read(pipefd[0], buff, sizeof(buff));
printf("buff = %s\\n", buff);
close(pipefd[0]);
}
else
{
perror("fork error");
}
return 0;
}
二、有名管道
有名管道: 可以用在同一主机,任意进程间通信。

1. 阻塞特性
有名管道默认阻塞打开
- 以只读 (O_RDONLY)打开管道:程序会阻塞等待,直到另一个进程以只写 (O_WRONLY)打开该管道。
- 以只写 (O_WRONLY)打开管道:程序会阻塞等待,直到另一个进程以只读 (O_RDONLY)打开该管道。
通俗讲:读端等写端、写端等读端,两边都到位之后,通信才开始。
2. 读写行为规则
(1)写管道:管道缓冲区满的时候,write()会阻塞,直到读端取走一部分数据。
(2)读管道:
管道内有数据:read立刻读到数据返回。
管道为空:默认阻塞,等待写端写入数据。
(3)断开情况
读端全部关闭,写端继续写数据 → 写进程收到 SIGPIPE 管道破裂信号,进程异常终止
写端全部关闭,读端去读管道 → read 返回 0,代表读到文件末尾
3. 缓冲区大小
有名管道数据存放在内核缓冲区,磁盘上的 fifo 文件只作为一个入口标识,不会存储传输的数据。 默认缓冲区大小:4096字节,数据读完,缓冲区的数据就消失。
4. 半双工特性
有名管道属于半双工通信,同一时刻只能单向传递数据。
如果需要双向收发,必须创建两条独立的有名管道,一条负责 A→B,另一条 B→A。
5. 生命周期
- mkfifo 创建出来的管道文件永久保存在磁盘;
- 进程退出不会自动删除管道文件;
- 用完后需要手动删除:remove("myfifo") 或者终端命令 rm myfifo。
6. 操作流程
(1)创建有名管道文件:mkfifo();
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
权限赋0664就可以
返回值:
成功:0
失败:-1
(2)打开管道文件:open();
(3)写管道文件:write();
(4)读管道文件:read();
(5)关闭管道文件:close();
补充:remove();删除一个文件或目录
练习1:一个进程从终端读数据、另一个进程打印该数据
###read.c
int main(void)
{
int ret = mkfifo("fifo",0664);
if(-1 == ret && errno != EEXIST)
{
perror("mkfifo error");
return -1;
}
int fd = open("fifo",O_RDONLY);
if(-1 == fd)
{
perror("open fifo error");
return -1;
}
char buff[1024] = {0};
while(1)
{
memset(buff,0,sizeof(buff));
size_t sizeread(fd,buff,sizeof(buff));
if(size <= 0)//=0 close(write) <0 error
{
break;
}
printf("buff = %s\\n",buff);
}
close(fd);
remove("fifo");
return 0;
}
###write.c
int main(void)
{
int ret = mkfifo("fifo", 0664);
if (-1 == ret && errno != EEXIST)
{
perror("mkfifo error");
return -1;
}
int fd = open("fifo", O_WRONLY);
if (-1 == fd)
{
perror("open fifo error");
return -1;
}
char buff[1024] = {0};
while(1)
{
fgets(buff, sizeof(buff), stdin);
if(0==strcmp(buff,".quit\\n"))
{
break;
}
write(fd,buff,strlen(buff));
}
close(fd);
//remove("fifo");
return 0;
}
练习2:将一个.png图片通过管道拷贝
#####write.c
int main(int argc, const char *argv[])
{
if (argc < 2)
{
printf("Usage : ./a.out <filename>\\n");
return -1;
}
mkfifo("myfifo", 0664);
int fdfifo = open("myfifo", O_WRONLY);
if (fdfifo < 0)
{
perror("open fifo error");
return -1;
}
int fd = open(argv[1], O_RDONLY);
if (fd < 0)
{
perror("open error");
return -1;
}
char buff[1024] = {0};
ssize_t ret = 0;
while ((ret = read(fd, buff, sizeof(buff))) > 0)
{
write(fdfifo, buff, ret);
}
close(fd);
close(fdfifo);
return 0;
}
#####read.c
int main(int argc, const char *argv[])
{
if (argc < 2)
{
printf("Usage : ./a.out <filename>\\n");
return -1;
}
mkfifo("myfifo", 0664);
int fdfifo = open("myfifo", O_RDONLY);
if (fdfifo < 0)
{
perror("open fifo error");
return -1;
}
int fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0664);
if (fd < 0)
{
perror("open error");
return -1;
}
ssize_t ret = 0;
char buff[1024] = {0};
while ((ret = read(fdfifo, buff, sizeof(buff))) > 0)
{
write(fd, buff, ret);
}
close(fd);
close(fdfifo);
return 0;
}
三、对比
| 通信范围 | 仅限父子 / 兄弟(有血缘)进程 | 任意两个无血缘进程 |
| 磁盘文件 | 无文件,内核缓冲区 | 存在管道文件标识 |
| 打开方式 | pipe 一次性创建读写两端 | 必须先用 mkfifo 创建,再 open 打开 |
网硕互联帮助中心






评论前必须登录!
注册