云计算百科
云计算领域专业知识百科平台

Linux应用层开发 --- 管道

管道:属于 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有名管道 fifo
通信范围 仅限父子 / 兄弟(有血缘)进程 任意两个无血缘进程
磁盘文件 无文件,内核缓冲区 存在管道文件标识
打开方式 pipe 一次性创建读写两端 必须先用 mkfifo 创建,再 open 打开
赞(0)
未经允许不得转载:网硕互联帮助中心 » Linux应用层开发 --- 管道
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!