前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >【Linux进程通信】三、命名管道

【Linux进程通信】三、命名管道

作者头像
利刃大大
发布2025-03-18 12:55:09
发布2025-03-18 12:55:09
5700
代码可运行
举报
文章被收录于专栏:csdn文章搬运csdn文章搬运
运行总次数:0
代码可运行

Ⅰ. 命名管道的概念

​ 有了匿名管道的知识,命名管道对我们来说就不是很难了!

​ 匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。如果我们想在不相关的进程之间交换数据,可以创建和使用 FIFO 文件来做这项工作,它经常被称为命名管道

命名管道是一种特殊类型的文件!

Ⅱ. 命名管道的创建

​ 命名管道既可以在命令行上创建,也可以在程序中创建!

一、在命令行上创建

代码语言:javascript
代码运行次数:0
运行
复制
mkfifo filename

​ 下面我们写个脚本向这个管道文件中不断输入数据观察一下结果怎么样!

​ 如下图可以看到如果向管道文件不断的写入信息,那么 管道文件的大小还是保持为 0 不变!为什么呢❓❓❓

​ 其实就是因为操作系统知道这是一个管道文件,知道这个文件是管道类型,所以对于管道文件和普通文件的处理方式不一样,对于普通文件来说它加载到内存中,我们不断的向普通文件里面写入数据,它是会不断的刷新到磁盘上的(与刷新策略有关),所以如果这里是普通文件的话那么我们会看到大小一直在变;而如果是管道文件,它被加载到内存之后,无论是对它写入还是刷新,它都不会刷新到磁盘上面,这样子一来就达到了内存级别的交互,大大的提高了效率,和我们的匿名管道是类似的!

​ 再看下面的结果,我们仅仅使用了 cat 指令一次,就不断的循环将写入管道的数据一个一个的读到屏幕上面,说明只要有信息在向管道文件写入的时候,我们读取管道文件,会进入阻塞状态一直等待写入的信息!而对于普通文件,则不会有读取的时候阻塞是情况!

那么命名管道是如何做到让不同的进程看到同一份资源的❓❓❓

​ 其实是因为操作系统可以让不同的进程打开指定名称(路径+文件名)的同一个文件,而对于同一个目录下,是不允许有同名文件的,这也就确定了文件的唯一性!这也就是为什么命名管道叫做命名管道,因为**它是通过文件名称来标定唯一性的!而对于匿名管道**来说,它是通过父子进程的继承属性来实现的!

二、在程序中创建

代码语言:javascript
代码运行次数:0
运行
复制
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char* pathname, mode_t mode);
// 功能:创建一个fifo特殊文件也就是命名管道
// 参数:pathname代表创建该文件的路径,mode代表创建文件的权限,会受掩码影响,可使用umask(0)将掩码设为0
// 返回值:成功返回0,失败返回-1,并且有可能设置错误信息errno

​ 接下来我们来写一串代码,让 server.cpp 负责打开通信、接收信息和关闭通信,让 client.cpp 负责发送信息,并且我们将它们的公共信息放在头文件 commom.hpp 中(.hpp 指用 C/C++ 语言编写的头文件,通常用来定义数据类型,声明变量、函数、结构和类),所以在程序中要实现的话,我们就得用在程序中创建管道文件的语言级别接口 mkfifo !下面代码中默认将管道文件放到系统中的共享目录 /tmp 中!

​ 下面还会配合一个我们以前使用过的接口函数 unlink ,它是用来移除目录项的,当然也可以用 rm 来删除,但是我们这里可以用用 unlink

代码语言:javascript
代码运行次数:0
运行
复制
#include <unistd.h>
int unlink(const char *path);
// 功能:移除一个目录项目
// 参数:path表示要移除目录的路径
// 返回值:成功移除返回0,失败则返回-1,并且设置错误码,源文件内容不变

三、简单实现两个程序之间通信

Makefile

代码语言:javascript
代码运行次数:0
运行
复制
.PHONY : all
all : server client

server : server.cpp
	g++ -o $@ $^ -std=c++11 -g
client : client.cpp
	g++ -o $@ $^ -std=c++11 -g 

.PHONY : clean
clean:	
	rm -f server client

common.hpp

代码语言:javascript
代码运行次数:0
运行
复制
#pragma once
#include <iostream>
#include <string>
#include <cassert>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

#define NAMED_PIPE "/tmp/my_named_pipe"    // 存放到系统中的共享目录下

bool createFifo(const std::string& path)
{
    umask(0);
    int n = mkfifo(path.c_str(), 0666); // 创建管道文件
    if(n == 0)
        return true;
    else 
    {
        std::cout << "errno: " << errno << " err string: " << strerror(errno) << std::endl;
        return false;
    }
}

void removeFifo(const std::string& path)
{
    int n = unlink(path.c_str()); // 移除管道文件目录项
    assert(n == 0);
    (void)n;
}

server.cpp

代码语言:javascript
代码运行次数:0
运行
复制
#include "commom.hpp"
int main()
{
    // 创建管道
    bool flag = createFifo(NAMED_PIPE);
    assert(flag);
    (void)flag;

    // 打开文件后,接收信息,最后关闭文件
    std::cout << "server begin" << std::endl;
    int rfd = open(NAMED_PIPE, O_RDONLY);
    std::cout << "server end" << std::endl;
    if(rfd < 0)
        exit(1);

    char buffer[1024];
    while(true)
    {
        ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);
        if(n > 0)
        {
            buffer[n] = '\0';
            std::cout << "client->server# " << buffer << std::endl;
        }
        else if(n == 0)
        {
            std::cout << "client quit, me too!" << std::endl;
            break;
        }
        else
        {
            std::cout << "errno: " << errno << " err string: " << strerror(errno) << std::endl;
            break;
        }
    }

    close(rfd);
    // 删除管道
    removeFifo(NAMED_PIPE);
    return 0;
}

client.cpp

代码语言:javascript
代码运行次数:0
运行
复制
#include "commom.hpp"
int main()
{
    // 打开文件后发送信息,最后关闭文件
    std::cout << "client begin" << std::endl;
    int wfd = open(NAMED_PIPE, O_WRONLY | O_TRUNC, 0666);
    std::cout << "client end" << std::endl;
    if(wfd < 0) 
        exit(1);

    char buffer[1024];
    while(true)
    {
        std::cout << "Please Say# ";
        fgets(buffer, sizeof(buffer), stdin); // 从键盘写到buffer
        buffer[strlen(buffer) - 1] = '\0'; // 将最后的回车去掉,不必担心会得到buffer[-1],因为我们始终要按一次回车

        // 将buffer写入文件
        ssize_t n = write(wfd, buffer, strlen(buffer)); 
        assert(n == strlen(buffer));
        (void)n;
    }

    close(wfd);
    return 0;
}

​ 接下来我们运行一下看看效果:

① 先执行 client 的情况

​ 可以看到 client 进程结束掉了,这是因为我们还没有执行 server,那么其就不会创建管道文件。如果我们在 client 中是以 O_CREAT 顺便创建了管道文件,那么我们执行 server 的时候会发现管道文件已经存在,会直接触发 assert 断言的,所以一定要先开启 server

② 先执行 server 的情况

​ 这种情况就不会出现以上的问题了,那么我们来看看会有什么效果:

​ 若没有还没有打开 client,那么 server 就会像上图一样,卡在 open 函数那里,因为我们学了匿名管道的读写规则也知道,如果管道的写入端没有启动,那么读取端的话会一直阻塞等待写入端的启动

​ 下面我们看看 client 打开后的情况:

​ 我们可以看到这样子就实现了正常的程序之间的通信啦!client 发送信息后,server 进行接收!期间我们也可以发现 如果 client 还没有发送消息的话,但是 client 的进程是正在执行的,那么 server 就会一直阻塞着等待 client 的写入

Ⅲ. 匿名管道与命名管道的区别

  • 匿名管道pipe 函数创建并通过文件描述符 pipefd[2] 进行操作
  • 命名管道mkfifo 函数创建,打开用 open 函数
  • FIFO(命名管道)与 pipe(匿名管道)之间 唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

Ⅳ. 命名管道的打开规则

  • 如果当前打开操作是为 而打开 FIFO 时:
    • O_NONBLOCK disable阻塞直到有相应进程为写而打开该 FIFO
    • O_NONBLOCK enable立刻返回成功
  • 如果当前打开操作是为 而打开 FIFO 时:
    • O_NONBLOCK disable阻塞直到有相应进程为读而打开该 FIFO
    • O_NONBLOCK enable立刻返回失败,错误码为 ENXIO
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-03-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Ⅰ. 命名管道的概念
  • Ⅱ. 命名管道的创建
    • 一、在命令行上创建
    • 二、在程序中创建
    • 三、简单实现两个程序之间通信
      • ① 先执行 client 的情况
      • ② 先执行 server 的情况
  • Ⅲ. 匿名管道与命名管道的区别
  • Ⅳ. 命名管道的打开规则
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档