当前位置: 首页 > article >正文

[操作系统] 进程间通信:命名管道原理与操作

文章目录

  • 前提引入
  • 创建命名管道
    • 命令行创建
    • 程序代码创建
    • 命名管道的打开规则
  • 实际应用示例
    • 示例 1:文件复制
    • 示例 2:服务器-客户端通信
  • 总结

前提引入

通过对匿名管道的学习,可以理解两个具有亲缘关系的进程之间的通信方式,通常是父子进程之间的通信。

那如果想让两个不相关的进程之间通信应该怎么做呢?

我们通常使用FIFO文件来完成通信工作,这个文件也就是所谓的命名管道。Linux在一切皆文件,匿名管道是文件,命名管道也是文件。任何知道其文件名的进程都可以访问它,即使这些进程没有亲缘关系。

匿名管道的原理就是父进程和子进程都可以看到同一个文件,命名管道的原理也是如此。

当进程A打开一个路径/a/b/c.txt,进程B也打开一个路径/a/b/c.txt,那么操作系统会将一个文件在内存中加载两次吗?

当然不会,完全没有必要。

管道文件有路径有名字,并且路径具有唯一性,这就是为什么叫命名管道的原因。

所以要如何让两个毫无关系的进程可以看到同一个文件呢?如图所示,这时候就需要管道文件了。可以将管道文件看做普通文件,但是它有着自己的特性,所以又和普通文件不同。

当两个进程想要打开这个管道文件时,前面的操作和普通文件相同,会在自己的文件描述符表占用一个位置然后得到一个struct file。但是操作系统并不会将inode文件内容加载到内存两次来提供给struct file使用,而是两个struct file指向同一份inode数据

通过这样就做到了没有关系的两个进程可以看到同一个文件,并且可以对该文件进行操作。

这个文件就是管道文件。

管道文件并不是真正的普通文件,

主要特点:

  • 文件系统可见:命名管道在文件系统中以文件形式存在,可通过文件名访问。
  • 持久性:创建后,除非显式删除(如使用 unlink 或 rm),它会一直存在。
  • 单向通信:数据流是单向的,通常一个进程写入,另一个进程读取。如果需要双向通信,则需创建两个 FIFO。
  • 默认阻塞:读写操作会阻塞,直到读端和写端都打开,除非设置为非阻塞模式。
  • 管道文件在创建后,进程只需要打开它进行读写操作,而不需要像某些文件那样频繁刷新缓冲区。管道的读写操作是直接基于内存的,效率较高。

创建命名管道

命名管道可以通过命令行或程序代码创建。

命令行创建

在终端中使用 mkfifo 命令:

$ mkfifo myfifo

这会在当前目录下创建一个名为 myfifo 的命名管道。使用 ls -l 查看时,文件类型字段会显示 p,例如:

prw-r--r-- 1 user user 0 Oct 10 12:00 myfifo

程序代码创建

在 C/C++ 中,使用 <font style="color:black;">mkfifo</font> 函数:

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

int mkfifo(const char *filename, mode_t mode);
  • <font style="color:black;">filename</font>:命名管道的路径名。
  • <font style="color:black;">mode</font>:文件权限,例如 <font style="color:black;">0644</font>(所有者可读写,其他人可读)。
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    if (mkfifo("myfifo", 0644) == -1) {
        perror("mkfifo failed");
        exit(EXIT_FAILURE);
    }
    printf("命名管道 'myfifo' 创建成功。\n");
    return 0;
}

运行后,文件系统会多一个名为 <font style="color:black;">myfifo</font> 的命名管道。

命名管道的打开规则

命名管道通过 open 系统调用打开,其行为取决于打开模式(O_RDONLYO_WRONLY)和是否设置 O_NONBLOCK 标志:

  1. 以读模式打开(O_RDONLY)
  • 默认(阻塞):open 会阻塞,直到有进程以写模式打开同一 FIFO。
  • 非阻塞(O_NONBLOCK):立即返回成功,即使没有写端。
  1. 以写模式打开(O_WRONLY)
  • 默认(阻塞):open 会阻塞,直到有进程以读模式打开同一 FIFO。
  • 非阻塞(O_NONBLOCK):如果没有读端,open 失败,返回错误码 ENXIO。

这些规则确保读写两端同步,避免数据丢失或读取错误。

实际应用示例

示例 1:文件复制

以下示例展示如何使用命名管道将文件 <font style="color:black;">source.txt</font> 的内容复制到 <font style="color:black;">dest.txt</font>

写进程:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>

int main() {
    mkfifo("fifo", 0644);  // 创建命名管道
    int infd = open("source.txt", O_RDONLY);  // 打开源文件
    if (infd == -1) { perror("open source"); exit(1); }
    int outfd = open("fifo", O_WRONLY);  // 打开 FIFO 以写入
    if (outfd == -1) { perror("open fifo"); exit(1); }
    char buf[1024];
    int n;
    while ((n = read(infd, buf, 1024)) > 0) {
        write(outfd, buf, n);  // 写入 FIFO
    }
    close(infd);
    close(outfd);
    return 0;
}

读进程:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>

int main() {
    int outfd = open("dest.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);  // 打开目标文件
    if (outfd == -1) { perror("open dest"); exit(1); }
    int infd = open("fifo", O_RDONLY);  // 打开 FIFO 以读取
    if (infd == -1) { perror("open fifo"); exit(1); }
    char buf[1024];
    int n;
    while ((n = read(infd, buf, 1024)) > 0) {
        write(outfd, buf, n);  // 写入目标文件
    }
    close(infd);
    close(outfd);
    unlink("fifo");  // 删除 FIFO
    return 0;
}

运行方式:

  1. 编译两个程序:<font style="color:black;">gcc writer.c -o writer</font><font style="color:black;">gcc reader.c -o reader</font>
  2. 在两个终端分别运行 <font style="color:black;">./writer</font><font style="color:black;"></font><font style="color:black;">./reader</font>
  3. <font style="color:black;">source.txt</font> 的内容会被复制到 <font style="color:black;">dest.txt</font>

示例 2:服务器-客户端通信

以下示例展示命名管道如何实现简单的服务器-客户端通信。

服务器端:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    mkfifo("chatfifo", 0644);  // 创建命名管道
    int rfd = open("chatfifo", O_RDONLY);  // 以读模式打开
    if (rfd < 0) { perror("open"); exit(1); }
    char buf[1024];
    while (1) {
        printf("等待客户端消息...\n");
        ssize_t s = read(rfd, buf, sizeof(buf) - 1);
        if (s > 0) {
            buf[s] = '\0';
            printf("客户端说: %s\n", buf);
        } else if (s == 0) {
            printf("客户端退出,服务器关闭。\n");
            break;
        }
    }
    close(rfd);
    unlink("chatfifo");
    return 0;
}

客户端:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main() {
    int wfd = open("chatfifo", O_WRONLY);  // 以写模式打开
    if (wfd < 0) { perror("open"); exit(1); }
    char buf[1024];
    while (1) {
        printf("请输入消息# ");
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof(buf) - 1);  // 从标准输入读取
        if (s > 0) {
            buf[s] = '\0';
            write(wfd, buf, strlen(buf));  // 写入 FIFO
        }
    }
    close(wfd);
    return 0;
}

运行方式:

  1. 编译:<font style="color:black;">gcc server.c -o server</font><font style="color:black;">gcc client.c -o client</font>
  2. 先运行 <font style="color:black;">./server</font>,再运行 <font style="color:black;">./client</font>
  3. 在客户端输入消息,服务器会显示接收到的内容。

总结

命名管道(FIFO)是 Linux 中一种简单而高效的进程间通信工具。通过命令行或代码创建后,它可以在文件系统中持久存在,适用于任意进程之间的单向数据交换。


http://www.kler.cn/a/612242.html

相关文章:

  • 使用ProcessBuilder执行FFmpeg命令,进程一直处于阻塞状态,一直没有返回执行结果
  • PHP MySQL 预处理语句
  • 基于yolov11的铁路轨道铁轨缺陷检测系统python源码+pytorch模型+评估指标曲线+精美GUI界面
  • Excel处理控件Aspose.Cells指南:如何在不使用 Microsoft Excel 的情况下解锁 Excel 工作表
  • 结合代码理解Spring AOP的概念(切面、切入点、连接点等)
  • vue watch数据监听
  • 关于spark在yarn上运行时候内存的介绍
  • 【Minio-优化浅谈】
  • CI/CD(六) helm部署ingress-nginx(阿里云)
  • 【后端】【Django】信号使用详解
  • C#面向对象 一些细节
  • 基于C++实现一个平面上的形状编辑程序
  • ChatGPT 4o 更新了图像能力,效果怎么样?
  • 青否数字人直播系统包括六大互动功能,保障直播间能够实现智能化实时互动!
  • RSA算法深度解析:从数学基础到安全实践
  • Docker容器的kafka在VM虚拟机挂起重新运行之后连接异常解决
  • 【人工智能】一部正在书写的传奇,从诞生到未来蓝图
  • 【力扣hot100题】(007)无重复字符的最长子串
  • Rust从入门到精通之进阶篇:17.宏编程基础
  • 排序算法(插入,希尔,选择,冒泡,堆,快排,归并)