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

初识Linux · 命名管道

目录

前言:

代码编写


前言:

有了前文匿名管道的基础,我们介绍匿名管道的时候就轻松许多了,匿名管道和命名管道的区别主要是在于,匿名管道不需要文件路径,并且匿名管道常用于父子进程这种具有血缘关系的场景,使用命名管道的时候,我们常常用于的情况是两个进程毫无联系,使这两个毫无关系的进程可以进行通信。

对于匿名管道来说,我们知道文件对象以及文件对象里面的文件对象里面属性集合,操作集合都不会重新创建,对于命名管道来说也是一样的,所以对于内核级别的文件缓冲区也是这个样子的,OS就没有必要创建两个了,毕竟浪费空间时间的事OS可不想做。

以上其实算是对于命名管道的原理的部分的简单介绍,其实和匿名管道差不多,本文的主要内容其实还是命名管道的代码编写。


代码编写

那么准备工作是先创建三个文件,分别表示客服端,服务端,以及创建管道的文件,创建命名管道之后,让另外两个进程分别打开管道。

那么我们的第一个任务是了解创建命名管道的函数->mkfifo:

直接man mkfifo查询到的是1号手册的mkfifo,那么我们可以使用试试:

创建了对应管道文件之后,我们可以发现几个特征点,它的名字后面带有| 代表管道的意思,并且,它的文件类型部分的字母对应的是p,也就是Pipe部分了,这个其实在文件权限部分介绍过。

还有一个有意思的点是在于……留个关子。

那么这是命令行部分创建命名管道,我们是要直接应用于代码层面,所以介绍3号手册的函数mkpipe:

对应n个头文件,对于返回值来说的话,如果创建管道成功的话,返回的值是0,出错了,返回的值就是-1,并且错误码被设置,所以如果返回出错我们可以打印对应的错误码看看,我们现在不妨试试:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string>
#include <cstdio>
#include <cerrno>


const std::string path = "./mypipe";

int CreateFifo()
{
    int n = mkfifo(path.c_str(),0666);
    if(n < 0)
    {
        perror("mkfifo");
    }

    return n;
}

这里使用的头文件相对来说也是比较多的,毕竟涉及到了string mkfifo perror,所以C++的头文件有,C++版的C语言头文件也是有的,在namedpipe文件里面实现好了该函数之后,我们转到server.cc文件里面进行调用,其实在client.cc里面调用都可以,毕竟之后不过就是一个进程作为读端,一个进程作为写端,所以任意调用,这里使用server.cc:

#include "namedPipe.hpp"
int main()
{
    CreateFifo();

    return 0;
}

这里需要插一个题外话了,我们经常是需要Make文件的,但是makefile好像只能编译一个文件?所以我们需要对原来的makefile进行简单的修改:

.PHONY:all
all:client server

client:client.cc
	g++ -o $@ $^ -std=c++11
server:server.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -rf server client

因为makefile是从上到下查找的,所以我们形成一种依赖关系,从而实现两种文件的编译即可。

那么现在我们尝试编译一下server.cc文件:

也是成功创建了,那么我们再运行一下试试?

也就成功的报错了,提示说文件已经存在了。

那么创建了管道文件我们总得删除管道吧?

可以使用函数unlink:

直接给对应的文件路径就可以了。

可是问题来了,我们现在能保证创建多个管道,但是每次创建管道都要使用函数,每次还要手动的调用,难道这不是很麻烦吗?我们使用的语言难道不是面向对象的C++语言吗?所以我们不妨封装一个对象:

class namepipe
{
public:
    namepipe(const std::string fifo_path)
        :_fifo_path(_fifo_path)
    {}

    ~namepipe()
    {
        int res = unlink(_fifo_path.c_str());
        if(res != 0)
        {
            perror("unlink");
        }
    }

private:
    const std::string _fifo_path;
    int _fd;
    int _id;
};

像这样,封装一个对象的好处是,我们不用自己手动的去销毁管道,因为实例化的对象出了main函数的栈帧自己就调用对应的析构函数了。

那么我们可以这个基础之上,进行一些细节的补充,比如是谁调用的?所以我们可以定义一个宏,表示是谁定义的,然后用宏来初识_id:

#define Creater 1
#define User 2
#define Read O_RDONLY
#define Write O_WRONLY
class namepipe
{
public:
    namepipe(const std::string fifo_path, int who)
        : _fifo_path(_fifo_path), _id(who)
    {
        if (_id == Creater)
        {
            int res = mkfifo(_fifo_path.c_str(), 0666);
            if (res != 0)
            {
                perror("mkfifo");
            }
            std::cout << "Creater create named pipe" << std::endl;
        }
    }

    ~namepipe()
    {
        if (_id == Creater)
        {

            int res = unlink(_fifo_path.c_str());
            if (res != 0)
            {
                perror("unlink");
            }
            std::cout << "Creater free named pipe" << std::endl;
        }
    }

private:
    const std::string _fifo_path;
    int _fd;
    int _id;
};

我们使用宏分别创建者的好处还有就是,client调用的时候我们就可以直接通过宏判断是谁创建的,这样就可以省略不必要的构造。

可是现在有一个问题就是,我们已经知道谁创建的,知道了对应的路径,可是打开的文件呢?这是非常重要的,所以我们引入一个变量,_fd,那么可以给一个默认的文件描述符,比如-1,初始化的时候总得初始化上去吧?

    namepipe(const std::string fifo_path, int who,int fd = DefaultFd)
        : _fifo_path(_fifo_path), _id(who),_fd(DefaultFd)
    {
        if (_id == Creater)
        {
            int res = mkfifo(_fifo_path.c_str(), 0666);
            if (res < 0)
            {
                perror("mkfifo");
            }
            std::cout << "Creater create named pipe" << std::endl;
        }
    }

对于构造函数和析构函数这里已经差不多了,那么剩下的就是文件打开,怎么操作管道的事儿了。

我们先来操作打开管道:

    bool OpenforRead()
    {
        return OpenNamePipe(Read);
    }
    bool OpenforWrite()
    {
        return OpenNamePipe(Write);
    }

打开管道我们通过宏的不同传参,就可以保证管道的打开是通过读的方式打开的还是写的方式打开的:


private:
    bool OpenNamePipe(int mode)
    {
        _fd = open(_fifo_path.c_str(), mode);
        if (_fd < 0)
            return false;
        return true;
    }

但是毕竟涉及到了_fd的修改,所以我们不希望直接可以调用,那么将它私有是最好的选择,这是打开方式的方法,最后的就是写入读取的方法了:

    int ReadNamePipe(std::string *out)
    {
        char buffer[BaseSize];
        int n = read(_fd, buffer, sizeof(buffer));
        if (n > 0)
        {
            buffer[n] = 0;
            *out = buffer;
        }
        return n;
    }
    int WriteNamePipe(std::string &in)
    {
        return write(_fd,in.c_str(),in.size());
    }

此时两个一整个管道相关的操作就完成了。

对于server:

#include "namedPipe.hpp"

int main()
{
    // CreateFifo();

    namepipe fifo(path, Creater);

    if (fifo.OpenforRead())
    {
        std::cout << "server open name pipe done" << std::endl;
        sleep(3);
        while (true)
        {
            std::string message;
            int n = fifo.ReadNamePipe(&message);
            if (n > 0)
            {
                std::cout << "Client Say> " << message << std::endl;
            }
            else if (n == 0)
            {
                std::cout << "Client quit, Server Too!" << std::endl;
                break;
            }
            else
            {
                std::cout << "fifo.ReadNamedPipe Error" << std::endl;
                break;
            }
        }
    }
    return 0;
}

对于client:

#include "namedPipe.hpp"

int main()
{
    namepipe fifo(path, User);
    if (fifo.OpenforWrite())
    {
        std::cout << "client open named pipe done" << std::endl;
        while (true)
        {
            std::cout << "Please enter> ";
            std::string message;
            std::getline(std::cin, message);
            fifo.WriteNamePipe(message);
        }
    }
    return 0;
}

试试结果?

符合预期。

如果我们运行./server的时候,不打开client,会发现./server也不会有后续动作,并且如果我们直接关掉写端,./server端是直接关闭的,这是上文匿名管道的知识点,实际上也是一种进程间同步!!


感谢阅读!


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

相关文章:

  • 工业相机选取
  • PostgreSQL中的COPY命令:高效数据导入与导出
  • 字节跳动核心技术:TT推荐系统从0-1落地应用
  • LLM时代下Embedding模型如何重塑检索、增强生成
  • 11 go语言(golang) - 数据类型:结构体
  • 【大数据学习 | kafka高级部分】kafka中的选举机制
  • 洛谷 P2239 [NOIP2014 普及组] 螺旋矩阵
  • lua 编译网路核心
  • 【系统架构设计师】2024年下半年真题论文: 论多源异构数据集成方法(包括参考素材)
  • 理解 FPGA 的关键知识点整理
  • Scala 中 set 的实战应用 :图书管理系统
  • 华为ensp防火墙配置(纯享版)
  • web——[GXYCTF2019]Ping Ping Ping1——过滤和绕过
  • 【日志】力扣58.最后一个单词的长度//14.最长公共前缀//28. 找出字符串中第一个匹配项的下标
  • PHP API的路由设计思路
  • Java基础面试题
  • strerror函数详解
  • JavaScript缓存之Service Worker workbox
  • Library:Day-02
  • qt QPixmapCache详解
  • 解决 Vue3、Vite 和 TypeScript 开发环境下跨域的问题,实现前后端数据传递
  • b4tman / docker-squid 可快速安装运行的、容器型代理服务器 + podman
  • contos7.9 部署3节点 hadoop3.4 集群 非高可用
  • 【.NET 8 实战--孢子记账--从单体到微服务】--简易权限--访问权限中间件
  • 深度学习:NAT Decoder 详解
  • 【GPTs】EmojiAI:轻松生成趣味表情翻译