初步了解Linux中的命名管道及简单应用和简单日志

命名管道原理

命名管道和匿名管道的原理是类似的,他们都是在内核里面有一块不同进程之间可以看到的公共"资源"。

而我们所说的命名管道就是这个资源,命名管道是对于不具有血缘关系的进程之间进行进程间通信的一种方式,既然不具有血缘关系,那么就说明了我们不会像父子进程一样,自动的拥有一块相同的资源,我们需要自己创建我们的资源。

这个"资源"本质上就是以文件,叫做通道文件,在Linux中一切皆文件,他拥有文件的特性,知道这一点,我们就将由于同一份资源转化为了打开同一个文件了,一个进程以写方式打开,另一个进程以读方式打开,这样我们就拥有了所谓的同一份"资源".

创建命名管道

在Linux中创建命名管道的指令为 mkfifo,创建的文件类型为p,表示管道(pipe)文件

创建命名管道文件的函数:mkfifo

参数:const char*pathname 传入创建该管道文件的路径

mode_t mode 文件的权限

返回值:创建成功返回0,失败则返回-1;

销毁命名管道的函数:unlink

参数:const char* path 文件路径

返回值:销毁成功返回0,失败则返回-1

当我们知道了怎么创建和销毁命名管道后,我们就可以根据他的原理来实现一个简单的进程间通信了。

命名管道的应用

接下来我们利用命名管道来简单实现一个进程间的通信

思路:

1、我们首先需要创建一个命名管道作为我们的共享资源,然后让两个进程分别以读的方式打开和以写的方式打开命名管道(命名管道是一个文件)

2、打开相关文件后,以读写文件的方式就可以从命名管道中写入或者读出数据

3、完成相关操作后销毁共享文件即可.

接下来我们利用server(读端)和cilent(写端)两个进程来进行通信,利用command头文件让两个进程都能看见共享资源。

函数设计:

command.hpp

**Init类:**用于创建命名管道,利用Init类来自动完成管道创建和销毁

cpp 复制代码
enum{                        //出错返回值
    FILE_CREATE_ERR  = 1,
    FILE_OPEN_ERR,
    FILE_DELETE_ERR

};

class Init
{
public:
    Init()
    {
        int n = mkfifo(FILE_FIFO, 0666);//FILE_FIFO 文件路径(宏)
        if (n == -1)
        {
            perror("mkfifio:");
            exit(FILE_CREATE_ERR);
        }
    }

    ~Init()//析构函数自动销毁pipe
    {
         int n = unlink(FILE_FIFO);
         if(n==-1)
         {
            perror("unlink:");
            exit(FILE_DELETE_ERR);
         }
    }
};

server.cc

cpp 复制代码
int main()
{
    Init init;

    //以读方式打开管道
    int fd = open(FILE_FIFO,O_RDONLY);
    if(fd < 0)
    {
        perror("open:");
        exit(0);
    }

    //读取消息

    while (true)//持续读取
    {
        char buffer[1024]={0};
        int n = read(fd,buffer,sizeof(buffer));
        if(n!=0)
        cout<<buffer<<endl;
        else 
        {
            cout<<"write qiut me too"<<endl;
            break;
        }
        
       
    }

    
    return 0;
}

client.cc

cpp 复制代码
int main()
{

    int fd = open(FILE_FIFO, O_WRONLY);//以写方式打开管道文件
    if (fd < 0)
    {
        perror("open:");
        exit(FILE_OPEN_ERR);
    }


    string s;
    while (true)//持续写入
    {
       
        cout<<"请输入 :";
        getline(cin,s);
        write(fd, s.c_str(), s.size());
    }

    close(fd);
    return 0;
}

运行结果如下:

client:

server:

根据以上思路我们就完成了一个简单的命名管道的进程间通信

日志

概念

日志(Log)是系统、应用程序或设备在运行过程中自动生成的记录文件,用于追踪事件、状态变化、错误信息或用户操作。日志通常以时间顺序存储,便于后续分析、调试或审计。

我们可以在我们的进程通信中使用日志来记录相关信息,接下来我们来制作一个简单日志。

知识补充:

在我们的日志中,我们需要有时间错误类型 ,以及我们自己像格式化输出的内容等信息,下面介绍能够完成上述操作的函数.

常见错误类型:

致命: Fatal最高系统 / 服务不可恢复的严重故障 ,直接导致进程退出、服务宕机,核心业务完全不可用数据库连接池耗尽且无法重建、配置文件加载失败、依赖的核心中间件(如 Redis/MQ)连接失败、内存溢出(OOM)、权限彻底缺失导致服务无法启动立即触发最高级别告警(电话 / 短信 / 企业微信钉钉强推)、日志永久持久化、纳入故障定级(P0/P1)

错误: Error高系统出现可感知的错误 ,单个功能 / 接口异常,但服务整体未宕机 ,部分业务受影响(非核心)接口调用超时、数据库单条 SQL 执行失败、参数校验不通过、第三方接口调用失败(有降级)、文件读写失败、局部空指针异常触发普通告警(企业微信 / 钉钉)、日志持久化、开发人员及时排查(24h 内)

警告: Warning中系统出现潜在风险 / 非预期状态 ,当前业务未受影响,但不处理可能升级为 ERROR/FATAL 连接池达到阈值(如使用率 80%)、缓存击穿 / 穿透(少量)、请求参数不规范(非致命)、临时文件未清理、定时任务延迟执行、数据库索引失效(单表)无实时告警,日志保留一定时间、运维 / 开发定期巡检(按天 / 周)、纳入风险监控

信息: Info低系统正常运行的关键节点信息,记录核心业务流程的执行轨迹,用于排查问题时的链路追溯服务启动 / 停止成功、核心接口的入参出参(关键业务)、用户登录 / 登出(核心操作)、定时任务执行成功、微服务间调用的关键节点、数据库连接成功无告警、日志按策略保留(如 7 天)、按需打印(避免过多)

调试: Debug最低系统开发 / 调试阶段的详细细节信息 ,记录代码执行的细粒度轨迹(如变量值、分支走向)函数内的变量打印、循环执行次数、SQL 完整语句、接口的全量入参出参、中间件的通信细节生产环境默认关闭、测试 / 开发环境开启、日志不持久化(内存临时存储)

时间函数:

time函数, 用于获取时间戳(1970-01-01 00:00:00以来的秒数)

输出型参数 :如果 tloc 不为 NULL,函数会将当前时间(以秒为单位)存储到 *tloc 指向的内存位置。这意味着调用者可以通过传入的指针直接获取返回的时间值。

返回值 :本质是long int类型的整数,进行了封装

localtime函数

形参:传入一个time_t 类型,也就是传入个时间戳,与time函数搭配使用

返回值:Linux内的一个内置结构体,包含各种时间信息(以1970-01-01 00:00:00 以来的秒数进行转换)

struct tm内的信息:

格式化输出函数:

vsnprintf函数

参数:char*str 向目标指针内写入

size_t size 写入的字节大小

char*format 自定义输出格式

va_list C中的可变参数类型

返回值:写入的字符字节个数,>0写入成功,返回-1则出现格式化错误

日志实现

将日志作为一个类型来进行管理:

函数设计:

所有宏的定义

cpp 复制代码
#define LogFile "log.txt"
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

#define Screen 1
#define Onefile 2
#define Classfile 3
Log()默认构造
cpp 复制代码
  Log()
    {
        printMethod = Screen;
        path = "./log/";
    }

    void Enable(int method)
    {
        printMethod = method;
    }
void Enable(int method),改变打印方式
cpp 复制代码
 void Enable(int method)
    {
        printMethod = method;
    }
string LevelToString(int level)
cpp 复制代码
  string LevelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Dubug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "";
        }
    }
void operator()(int level, char *format, ...)
cpp 复制代码
void operator()(int level, char *format, ...)//利用仿函数来使log使用更加方便
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);//获取时间

        char leftbuff[1024] = {0};//固定好我们的日志等级和时间输出格式

        snprintf(leftbuff, sizeof(leftbuff), "[%s %d-%d-%d %d-%d-%d]", LevelToString(level).c_str(), ctime->tm_year, ctime->tm_mon, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
        char rightbuff[1024] = {0};


        va_list s;  //va_list是C语言中的可变参数类型

        va_start(s, format);//用于在栈中寻找到我们传入的可变参数

        vsnprintf(rightbuff, sizeof(rightbuff), format, s);//进行格式化输出,s里面就是我们传入                   
                                              //的可变参数的数据,利用format和s交给该函数处理
        va_end(s);//将s清空

        string log;
        log = (string)leftbuff + (string)rightbuff;//将日志等级和时间,与我们自定义的输出相结合

        printLog(level, log);//调用打印函数
    }
void printLog(int level, const string &logtxt)
cpp 复制代码
    void printLog(int level, const string &logtxt)//将打印进行分类
                                                 //1、向显示器输出
                                                 //2、向一个文件输出  
                                                 //3、将同一种类型放在一个文件类
    {
        switch (printMethod)
        {
        case Screen:
            cout << logtxt << endl;//显示器输出
            break;
        case Onefile:
            printOneFile(LogFile, logtxt);//一个文件输出
            break;
        case Classfile:
            printClassFile(level, logtxt);//多个文件输出
            break;
        default:
            break;
        }
    }
void printOneFile(const std::string &logname, const std::string &logtxt)
void printClassFile(int level, const std::string &logtxt)
cpp 复制代码
   void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
        if (fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }


    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += LevelToString(level); // "log.txt.Debug/Warning/Fatal"
        printOneFile(filename, logtxt);
    }

实验结果:正如我们所料

相关推荐
霸道流氓气质1 分钟前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush44 分钟前
嵌入式linux学习记录十四、术语
linux·嵌入式
benben04411 分钟前
强化学习之DQN算法族(基于gymnasium开发)
算法
载数而行52018 分钟前
Linux 11 动态监控指令top
linux
小宇宙Zz24 分钟前
Maven依赖冲突
java·服务器·maven
swordbob26 分钟前
NIO的channel中什么是 fd(File Descriptor,文件描述符)
java·开发语言·nio
小小工匠1 小时前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
源分享1 小时前
Java线程同步的多种实现方法(非常详细)
java·开发语言·jvm
Luminous.1 小时前
C语言--day30
c语言·开发语言