侧边栏壁纸
  • 累计撰写 218 篇文章
  • 累计创建 59 个标签
  • 累计收到 5 条评论

进程间通信05:命名管道

barwe
2022-07-14 / 0 评论 / 0 点赞 / 1,067 阅读 / 2,815 字
温馨提示:
本文最后更新于 2022-07-14,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

什么是命名管道

匿名管道(pipe)是一种进程间通信的古老方式,它只能用于父子进程间的通信。对于没有亲缘关系的连个进程,匿名管道就无能为力了。此时可以借助于一个叫做 命名管道 的东西,顾名思义,命名管道与匿名管道应该是相似的,但是用途更加广泛。

Linux 中一切皆文件,通俗点来讲,进程之间可以通过文件交换数据。命名管道就是通过一种特殊的文件实现进程间的数据交流,这种文件也被称为 FIFO 文件,它是一种特殊类型的文件。stat.h 中定义了它的文件类型S_FIFO,提供了宏S_ISFIFO(m)判断一个文件是否是 FIFO 文件。

创建 FIFO 文件

命名管道 在文件系统中以文件的方式存在,对命名管道的管理与对文件的管理类似。

使用mkfifo函数来创建一个命名管道(FIFO)文件:

#include <sys/stat.h> // mkfifo
#include <sys/types.h> // mode_t

/* Create a new FIFO named PATH, with permission bits MODE.  */
int mkfifo (const char *path, mode_t mode);

FIFO 的应用场景:

  • FIFO 文件由操作系统自动创建,使用者无需手动建立中间文件
  • FIFO 是一个先进先出的队列,可以用于处理客户端消息等业务
  • ……

使用命名管道

mkfifo函数本质上是创建一个特殊的文件,称之为 命名管道文件 或者 FIFO 文件命名管道 本质上是通过 FIFO 文件实现的,命名就是指的 FIFO 文件的文件路径是唯一的,其使用方法与使用文件是类似的。因此使用一个命名管道只需要打开对应的 FIFO 文件进行操作即可。

但是与普通文件不同的是,我可以打开一个可读可写的普通文件,但是不能打开一个可读可写的 FIFO 文件,FIFO 文件只能以只读或者只写的方式打开。FIFO 文件是为了在进程间传递数据,如果可以可读可写的打开 FIFO 文件,那么进程就不知道哪些数据是自己需要读取的,因为读取的数据可能是自己刚写进去的,这与命名管道的设计初衷相悖。

使用open函数打开一个 FIFO 文件,以只读方式打开就是从命名管道中读取数据,以只写方式打开就是往命名管道中写入数据:

#include <fcntl.h> // open

open(const char *fifo_fp, O_RDONLY);
open(const char *fifo_fp, O_RDONLY | O_NONBLOCK);
open(const char *fifo_fp, O_WRONLY);
open(const char *fifo_fp, O_WRONLY | O_NONBLOCK);

O_NONBLOCK表示以 非阻塞 的方式打开 FIFO 文件。阻塞指的是,该文件至少得存在一个只读打开和一个只写打开,这样这个命名管道才是“活跃”的(管道的读端和写端都与进程建立了连接)。例如使用O_NONBLOCK打开一个只读的 FIFO 文件,该进程会一直等待,直到有另外一个进程以只写的方式打开同一个文件后,open函数才会调用成功并返回。不加O_NONBLOCK时,即时没有其他进程只写打开这个 FIFO 文件,open函数不会去读取这个 FIFO 文件(?),而是直接返回 -1。FIFO 文件默认以阻塞式的方式打开

写数据

#include <stdio.h>    // perror
#include <unistd.h>   // write close
#include <sys/stat.h> // mkfifo
#include <errno.h>    // errno
#include <fcntl.h>    // open O_WRONLY

#define FIFO "/tmp/test.fifo" // FIFO 文件路径

int main()
{
    // 创建 FIFO 文件并测试创建是否成功,文件权限为仅拥有者可读可写
    if (mkfifo(FIFO, S_IRUSR | S_IWUSR) < 0 && errno != EEXIST)
    {
        perror("make fifo failed");
        return -1;
    }
    
    // 以只读方式打开 FIFO 文件,返回文件描述符
    int fd = open(FIFO, O_WRONLY);
    
    // 向 FIFO 文件写入数据
    char s[] = "hello world";
    write(fd, s, sizeof(s));
    
    // 关闭 FIFO 文件
    close(fd);
    
    return 0;
}

然后运行该程序:

gcc fifo_wr.c -o fifo_wr && fifo_wr

程序会进入一直等待,因为我们默认以只写阻塞的方式打开的 FIFO 文件,程序会一直等,直到有其他进程以只读的方式打开这个 FIFO 文件。

此时检查文件系统中确实创建了这样一个特殊的文件:

$ ll /tmp/ | grep test.fifo
prw-------.  1 barwe               barwe                  0 7月  14 15:27 test.fifo|
  • 文件类型是 p,表示这一个命名管道文件(FIFO 文件)
  • 文件权限只允许所有者可读可写
  • 文件名称是我们定义的 test.fifo,还加了一个管道符(|)

读数据

用另一个程序来从命名管道读取数据:

#include <stdio.h>    // perror
#include <unistd.h>   // write close
#include <sys/stat.h> // mkfifo
#include <errno.h>    // errno
#include <fcntl.h>    // open O_WRONLY

#define FIFO "/tmp/test.fifo"
#define MAX_LEN 128

int main(){
    // 打开 FIFO 文件
    int fd = open(FIFO, O_RDONLY);
    if (fd < 0) {
        perror("open fifo file failed\n");
    }
    // 读取管道内容
    char line[MAX_LEN] = {};
    int n = read(fd, line, MAX_LEN);
    printf("read %d bytes from pipe: %s\n", n, line);

    // 关闭 FIFO 文件
    close(fd);

    // 删除 FIFO 文件
    unlink(FIFO);
    
    return 0;
}

然后运行该程序:

gcc fifo_rd.c -o fifo_rd && fifo_rd

会发现程序立即输出:

read 12 bytes from pipe: hello world

并且运行的第一个程序立即结束。

并且文件系统中的 test.fifo 文件也被删除掉了。

命名管道的安全问题

主要发生在多端写,一端读的情境下。写入端每次写数据应该写入一个相对完整的数据块,手动保证数据连贯。

0

评论区