• 【Linux】-进程间通信-命名管道文件(没有关系的进程间进行通信),以及写一个日志模板


    在这里插入图片描述
    💖作者:小树苗渴望变成参天大树🎈
    🎉作者宣言:认真写好每一篇博客💤
    🎊作者gitee:gitee
    💞作者专栏:C语言,数据结构初阶,Linux,C++ 动态规划算法🎄
    如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!


    前言

    今天博主开始给大家讲解一下命名管道文件,就是相比较于匿名管道,是有名字的,命名管道文件的特点进行没有任何关系的进程间进行通信,他的原理几乎和匿名管道差不多,但是还是有差距,博主在匿名管道哪里没讲开,因为需要两者对比的区分开才更有效果,他进行通信的时候和文件操作差不多,话不多说,开始进入正文讲解

    讲解逻辑:

    1. 演示命名管道文件
    2. 理解命名管道文件
    3. 编码实现

    一、演示命名管道文件

    我们在命令行当中通过指令来实现两个进程之间的通信,因为指令也是程序,每个指令互相都是没有关系的,我们使用mkfifo这个指令来创建命名管道文件

    mkfifo myfifo
    
    • 1

    在这里插入图片描述
    在这里插入图片描述

    通过上面的演示,我们echo往管道文件写入,但是他是阻塞的,等到有人来读取他,他才没有阻塞,所以我们得出来一个结论,我们管道文件只有在读写端都接入的时候,才会被打开。这也是管道文件的一个特性,符合同步互斥机制。

    管道文件的演示很简单,接下来我们直接来谈谈他的理解

    二、理解

    大家应该很奇怪,为什么叫理解,不讲原理吗,原因是我们的命名管道和匿名管道原理差不多,但是周边有点小不同,需要大家去理解一下。

    1. 如果两个不同的进程打开同一个文件的时候,os会打开几个文件?答案是一个,文件系统只需要管理一份就好,相信细心的小伙伴发现了·,博主将匿名的,叫做匿名管道,将命名的,叫做命名管道文件,说明在本质上我们的匿名管道不是我们用户看到的文件一样,

    在这里插入图片描述

    可以简单理解命名管道文件在普通文件和匿名管道之间,但是效率还是匿名管道强一点,因为少了一层拷贝,我们可以通过下面的脚本来观察,管道文件确实是不刷盘的。
    在这里插入图片描述
    博主使用一直往管道问价那里面然后读取,我们使用 ls -l | grep myfifo 去查看,发现文件大小一直为0,所以内容不在硬盘上。

    1. 进程间通信的本质是,让不同的进程看到不同的资源,有了上面的理解1,这份资源也是os给的,而且管道文件也不在硬盘上,博主这是云服务器,当我们在后台开机重启的时候,这个管道文件应该也随之消失,他的生命周期是随着内核的,而不是随着进程的,通过上面的演示也可以看出来,我们的程序结束了,他依旧存在,还有大家别重新打开xshell,然后发现管道文件还在,难道博主说的不对,你重新打开xshell只是bash这个进程重新加载了,就好比你关闭电脑上的一个软件重新启动一样,大家别搞错了,我们的Linux系统是一直在运行的。
    2. 通过理解2,我们创建了管道文件,是为了让不同进程看到同一份资源,也就是看到这个管道文件,那你是怎么知道这两个进程打开的是同一个文件呢??答案是同路径下同文件名,路径天生是唯一的,根据文件系统的知识,我们同路径下的文件名也是唯一的,所以通过这两点–唯一性,让不同的进程打开的是同一个文件。至此我们建立了命名管道文件。

    通过上面的理解,大家应该理解了命名管道文件,原理和匿名很相似,所以接下来博主直接带大家来编写大家,让大家更好的理解

    三,编写代码

    既然是命名管道文件,肯定不能像创建普通文件一样使用mkdir去创建,按照我们之前的经验,准确来说是进程间通信的本质,我们认为这个命名管道文件应该是由操作系统去创建,我们来看看怎么在代码中创建管道:
    在这里插入图片描述

    第一个参数是创建管道的路径和文件名,第二个参数文件

    用完想删除管道文件,使用unlink函数
    在这里插入图片描述

    传刚才文件的路径和文件名就可以了

    serverPipe.cc

    #include "fifo.hpp"
    
    //此进程是服务端,给客户端发送内容的
    int main()
    {
        //创建管道文件
        int n=mkfifo(PATHNAME,MODE);
        if(n<0)
        {
            perror("mkfifo:");
            exit(EXIT_MKFIFO);
        }
        
        //打开管道,等待读入方打开管道,这个才会打开
        int fd=open(PATHNAME,O_WRONLY);
        if(fd<0)
        {
            perror("open:");
            exit(EXIT_OPEN);
        }
    
        //进行通信
        string s;
        while(true)
        {
            cout << "Please Enter@ ";
            getline(cin, s);
            write(fd,s.c_str(),s.size());
        }
    
        //关闭管道
        close(fd);
    
    
        //删除管道文件
        int m=unlink(PATHNAME);
        if(m<0)
        {
            perror("unlink:");
            exit(EXIT_UNLINK);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    clientPipe.cc

    #include "fifo.hpp"
    
    //此进程是客户端,接收服务端发过来的消息
    int main()
    {
        //创建管道文件另一个进程已经帮助我们做好了,我们拿来使用就好了
    
        int fd=open(PATHNAME,O_RDONLY);
        if(fd<0)
        {
            perror("open:");
            exit(EXIT_OPEN);
        }
    
        char buffer[N];
        while(true)
        {
            buffer[0]=0;
            size_t n=read(fd,buffer,sizeof(buffer));
            if (n > 0)
            {
                buffer[n] = 0;
                cout << "server say# " << buffer << endl;
            }
            else if (n == 0)
            {
                printf("server quit, me too!, error string: %s, error code: %d\n", strerror(errno), errno);
                break;
            }
            else
                break;
        }
        close(fd);
        return 0;
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    fifo.hpp

    #pragma once
    
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    
    using namespace std;
    
    #define PATHNAME "./myfifo"  //创建管道文件的路径和文件名
    #define MODE 0664  //权限
    #define N 1024
    
    #define EXIT_MKFIFO -1
    #define EXIT_OPEN -2
    #define EXIT_UNLINK -3
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述
    这个理解起来不难,大家也看到效果了。读端会等待写端,所以也是具有同步互斥机制的

    但是代码不够优雅,博主选择将创建管道和删除管道,放到一个类里面,都放在头文件里面:

    fifo.hpp

    #pragma once
    
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    
    using namespace std;
    
    #define PATHNAME "./myfifo"  //创建管道文件的路径和文件名
    #define MODE 0664  //权限
    #define N 1024
    
    #define EXIT_MKFIFO -1
    #define EXIT_OPEN -2
    #define EXIT_UNLINK -3
    
    
    class Init
    {
    public:
        Init()
        {
            int n=mkfifo(PATHNAME,MODE);
            if(n<0)
            {
                perror("mkfifo:");
                exit(EXIT_MKFIFO);
            }
        }
        ~Init()
        {
            int m=unlink(PATHNAME);
            if(m<0)
            {
                perror("unlink:");
                exit(EXIT_UNLINK);
            }
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    四、编写日志代码

    我们发现每次写代码打印错误信息的时候,都是直接写的,而现在博主就交给你们一个办法,以后再学习中都可以使用到,而且这个以后去公司的时候,也不需要你自己去写的,我们了解这个原理就可以了,这里会介绍一些不常用的函数接口,接下来博主就来介绍一下什么是日志。

    4.1代码

    我们的日志是有等级之分的,先简单的来区分一下:

    #define INFO 0  //常规消息
    #define WARNING 1 //警告消息
    #define ERROR 2 //错误消息
    #define FATAL 3 //非常严重消息
    #define DEBUG 4 //调试消息
    
    • 1
    • 2
    • 3
    • 4
    • 5

    日志一般都会有时间的,所以要重点介绍一下获取时间的函数,其次日志等级是认为规定,所以传参要传等级,我们打印的格式以及要打印多少个信息也是我们认为控制的,所以最好传可变参数,这样就可以很好的控制了。类似于这样:
    log(INFO, "server open file done, error string: %s, error code: %d", strerror(errno), errno);

    博主先把代码粘贴出来,再来解释博主认为你们不理解的地方

    #include
    #include
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    
    //日志等级
    #define INFO 0  //常规消息
    #define WARNING 1 //警告消息
    #define ERROR 2 //错误消息
    #define FATAL 3 //非常严重消息
    #define DEBUG 4 //调试消息
    
    //打印方式
    #define SCREEN  0
    #define ONEFILE 1
    #define CLASSFILE 2
    
    #define SIZE 1024
    #define LOGTXT "log.txt" //默认的存放所有日志信息的文件
    class Log
    {
    public:
        Log()
        {
            printmethod=SCREEN;//默认的打印方式
            path="./log/";//默认的存放日志的路径
        }
        void enable(int method)
        {
            printmethod=method;//自定义打印方式
        }
        string leveltostring(int level)//将日志等级信息转换成字符串,打印的时候要使用到
        {
            switch(level)
            {
                case INFO:return "INFO:";
                case WARNING:return "WARNING:";
                case ERROR:return "ERROR:";
                case FATAL:return "FATAL:";
                case DEBUG:return "DEBUG:";
                default:return "NONE";
            }
        }
        void operator()(int level,const char* famat,...)
        {
            time_t t=time(nullptr);//获得时间戳
            struct tm*ctime=localtime(&t);//这是获取当地的一个时间,这个结构体李米娜有许多属性。
    
            char leftbuffer[SIZE];//存放日志时间等信息
            snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%d-%d-%d-%d:%d:%d]",leveltostring(level).c_str(),ctime->tm_year+1990,ctime->tm_mon+1,ctime->tm_mday,ctime->tm_hour,ctime->tm_min,ctime->tm_sec);
    
            va_list s;
            va_start(s,famat);
            char rightbuffer[SIZE];//存放日志等级信息,有用户去决定的
            vsnprintf(rightbuffer,sizeof(rightbuffer),famat,s);
            va_end(s);
    
            char logtxt[SIZE*2];//拼在一起
            snprintf(logtxt,sizeof(logtxt),"%s %s",leftbuffer,rightbuffer);
    
    
            printfmethod(level,logtxt);//打印方式
        }
    
        void printfmethod(int level,const string&logtxt)
        {
            switch(printmethod)
            {
                case SCREEN:
                    cout<<logtxt<<endl;
                break;
                case ONEFILE:
                    printfonefile(LOGTXT,logtxt);//本来一个参数就可以,但是为了适配下面函数的调用,加了一个参数
                break;
                case CLASSFILE:
                    printfclassfile(level,logtxt);
                break;
            }
        }
    
        void printfonefile(const string& logname,const string&logtxt)
        {
            string _logtxt=path+LOGTXT;
            int fd=open(_logtxt.c_str(),O_CREAT|O_WRONLY|O_APPEND);
            if(fd<0)
            {
                perror("open:");
                return;
            }
            write(fd,logtxt.c_str(),logtxt.size());
            close(fd);
    
        }
    
        void printfclassfile(int level,const string&logtxt)//多文件打印
        {
            string _logtxt=LOGTXT;
            _logtxt+=".";
            _logtxt+=leveltostring(level);//为了区分不同的日志等职,分别放到不同的文件里面。
            printfonefile(_logtxt,logtxt);
        }
        ~Log()
        {
    
        }
    private:
        int printmethod;//打印方式
        string path;//使日志文件和可执行程序不在一个目录下,单独建立一个目录
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114

    4.2 介绍知识点

    第一个:va_list是什么,这是类型,而va_start这其实是一个读取可变模板参数的起始位置的函数,我给大家据一个例子:任意数相加

    int sum(int n,...)
    {
        va_list s;
        va_start(s,n);
        int sum=0;
    
        while(n)
        {
            sum+=va_arg(s,int);
            n--;
        }
        va_end(s);
        return sum;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述
    在这里插入图片描述

    所以我们对于这种可变参数在一开始都至少有一个形参。这样大家应该知道作用了吧,我们在写rightbuffer这个字符串的时候还需要一个函数vsnprintf
    在这里插入图片描述
    这样就很明显看出来这个函数的作用了。

    第二个
    我们来看获取时间的函数,最重要的localtime·
    在这里插入图片描述

    这样一看就很清楚了,只是陌生函数多了,但是理解起来和用法都很简单。

    4.3 看加入日志的效果

    1. 往屏幕上打印:
      在这里插入图片描述

    通过打印出来的日志我们也可以发现,我们的管道只有双方都打开才会打开。这样打出来的消息就很规范

    1. 往一个文件里面打印
      通过log.enable(ONEFILE);去修改打印方式
      在这里插入图片描述

    2. 往多个文件打印,分类

    3. 通过log.enable(CLASSFILE);去修改打印方式
      在这里插入图片描述

    通过上面的演示,我们发现我们以后想要打印一些错误信息,就可以来复用这个模板了,让程序看上去非常的优雅。大家下去在好好的理解一下这个模板。

    五、总结

    今天讲解了命名管道,有了匿名管道的铺垫,我们在理解命名管道的时候就简单许多,所以操作来说也是很简单的,大家下去夺取理解就好了,我们今天讲的重点还有一个就是日志,这个让大家以后都可以重复使用的一个模板非常的方便,大家如果不想去实现,就使用博主写的吧,希望大家下去实现以下,这样更容易理解,而且更有印象。我们下篇讲解的是共享内存的知识,希望大家过啊里支持一下

  • 相关阅读:
    线性代数---第四章线性方程组
    CentOS7 离线部署 Python 项目
    什么是大数据测试?有哪些类型?应该怎么测?
    【ijkplayer】read_thread 流程梳理(二)
    100 个 Kotlin 面试问题及答案(其一)
    【Django】开发日报_1.1_Day:模板语法
    华为和华三(H3C),你总要选一个才行
    SystemVerilog:如何指定仿真源文件?
    C语言基础篇4:变量、存储、库函数
    2022 极术通讯-计算机体系结构将何去何从?
  • 原文地址:https://blog.csdn.net/qq_69369227/article/details/134490296