[C++]——同步异步日志系统(6)

news2024/12/24 2:16:19

同步异步日志系统

  • 一、日志器模块设计
    • 1.1 同步日志器模块设计
      • 1.1.1 局部日志器建造者模式设计
      • 1.1.2 同步日志器基本功能测试
    • 1.2 异步日志器模块设计
      • 1.2.1 单缓冲区设计
      • 1.2.2 异步工作线程的设计(双缓冲区思想)
      • 1.2.3 异步日志器设计
      • 1.2.4 异步日志器建造者模式设计
      • 1.2.5 异步日志器基本功能测试

一、日志器模块设计

功能:对前面所有功能进行整合,向外提供接口完成不同等级日志的输出。
管理的成员:

1.格式化模块对象
2.落地模块对象
3.默认的日志输出限制等级(大于等于限制输出等级的日志才能输出)
4.互斥锁(保证日志输出过程的线程安全,不会出现交叉日志)
5.日志名称(日志器的唯一标识,方便查找)

提供的操作:

debug等级日志的输出操作(分别封装日志消息LogMsg——各个接口日志等级不同)
info等级日志的输出操作
warn等级日志的输出操作
error等级日志的输出操作
fatal等级日志的输出操作

实现:
1.实现Logger基类(派生出同步日志器和异步日志器)
2.因为两种日志器的落地方式不同,需要将落地操作给抽象出来,不同的日志器调用不同的落地操作进行日志落地
3.模块关联过程中使用基类指针对子类日志器对象进行日志管理和操作

当前日志系统支持同步日志&异步日志,它们的不同点在于日志的落地方式上不同:
同步日志器:直接对日志消息进行输出
异步日志器:先将日志消息放到缓冲区,然后异步线程进行输出
因此:日志器类在设计的时候,先要设计一个Logger的基类,在Logger基类的基础上,继承出同步日志器(SyncLogger)和异步日志器(AsyncLoggrr)。

1.1 同步日志器模块设计

在这里插入图片描述

  1. 同步日志器的设计和具体框架
/*日志器模块
    1.先设计一个日志器的基类
    2.根据基类派生出不同的日志器
*/
#include "util.hpp"
#include "level.hpp"
#include "sink.hpp"
#include "format.hpp"
#include <memory>
#include <mutex>
#include <atomic>
namespace logslearn
{
    //  设计日志器基类
    class Logger
    {
        // 公有
    public:
        // 基类指针,用来控制继承子类的对象
        using ptr = std::shared_ptr<Logger>;
        // 操作方法
        // 构造日志消息对象并进行格式化,得到格式化后的日志消息字符串--然后进行落地输出,5个等级
        void debug(const std::string &file, size_t line, const std::string &fmt, ...); // 日志的输出操作
        void info(const std::string &file, size_t line, const std::string &fmt, ...);  // 日志的输出操作
        void warn(const std::string &file, size_t line, const std::string &fmt, ...);  // 日志的输出操作
        void error(const std::string &file, size_t line, const std::string &fmt, ...); // 日志的输出操作
        void fatal(const std::string &file, size_t line, const std::string &fmt, ...); // 日志的输出操作
    protected:
        // 日志落地,抽象接口完成实际的落地输出——不同的日志器会有不同的实际落地方式
        virtual void log(const char *data, size_t len) = 0;

    protected:
        std::mutex _mutex;                         // 互斥锁
        std::string _logger_name;                  // 日志器的名字
        std::atomic<loglevel::value> _limit_level; // 限制日志等级,atomic原子操作的意思是该操作执行过程中不能被中断,该操作要么不执行,要么全部执行,不存在执行一部分的情况。
        Formatter::ptr _formatter;                 // 控制格式化模块的对象
        std::vector<LogSink::ptr> _sliks;          // 这是一个数组,数组里存放日志落地方式的对象
    };
    // 派生出同步日志器
    class SyncLogger : public Logger
    {
    protected:
        // 重写虚函数
        void log(const char *data, size_t len)
        {
        }
    };
}
  1. 同步日志器的具体实现
#ifndef __M_LOGGER_H__
#define __M_LOGGER_H__
/*日志器模块
    1.先设计一个日志器的基类
    2.根据基类派生出不同的日志器
*/

#define _GNU_SOURCE
#include "util.hpp"
#include "level.hpp"
#include "sink.hpp"
#include "format.hpp"
#include <memory>
#include <mutex>
#include <atomic>
#include <cstdarg>

namespace logslearn
{
    //  设计日志器基类
    class Logger
    {
        // 公有
    public:
        // 基类指针,用来控制继承子类的对象
        using ptr = std::shared_ptr<Logger>;
        // 构造函数
        Logger(const std::string &logger_name, loglevel::value level, Formatter::ptr &formatter, std::vector<LogSink::ptr> &sinks) : _logger_name(logger_name), _limit_level(level), _formatter(formatter), _sliks(sinks.begin(), sinks.end()) {}
        // 操作方法
           //获取日志器名称
        const std::string& name(){
            return _logger_name;
        }
        // 构造日志消息对象并进行格式化,得到格式化后的日志消息字符串--然后进行落地输出,5个等级
        void debug(const std::string &file, size_t line, const std::string &fmt, ...)
        { // 日志的输出操作
            // 1.判断当前的日志是否达到输出等级
            if (loglevel::value::DEBUG < _limit_level)
            {
                return;
            } // 没有达到输出等级
            // 2.对fmt格式化字符串和不定参进行字符串组织,得到日志消息的字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == -1)
            {
                std::cout << "vasprintf failed!!\n";
                return;
            }
            va_end(ap); // 将ap指针置空
            // 代码一样,可以封装成一个函数
            serialize(loglevel::value::DEBUG, file, line, res);
            free(res); // 将指针释放掉
        }
        void info(const std::string &file, size_t line, const std::string &fmt, ...)
        { // 日志的输出操作
            // 1.判断当前的日志是否达到输出等级
            if (loglevel::value::INFO < _limit_level)
            {
                return;
            } // 没有达到输出等级
            // 2.对fmt格式化字符串和不定参进行字符串组织,得到日志消息的字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == -1)
            {
                std::cout << "vasprintf failed!!\n";
                return;
            }
            va_end(ap); // 将ap指针置空
            // 代码一样,可以封装成一个函数
            serialize(loglevel::value::INFO, file, line, res);
            free(res); // 将指针释放掉
        }
        void warn(const std::string &file, size_t line, const std::string &fmt, ...)
        { // 日志的输出操作
            // 1.判断当前的日志是否达到输出等级
            if (loglevel::value::WARN < _limit_level)
            {
                return;
            } // 没有达到输出等级
            // 2.对fmt格式化字符串和不定参进行字符串组织,得到日志消息的字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == -1)
            {
                std::cout << "vasprintf failed!!\n";
                return;
            }
            va_end(ap); // 将ap指针置空
            // 代码一样,可以封装成一个函数
            serialize(loglevel::value::WARN, file, line, res);
            free(res); // 将指针释放掉
        }
        void error(const std::string &file, size_t line, const std::string &fmt, ...)
        { // 日志的输出操作
            // 1.判断当前的日志是否达到输出等级
            if (loglevel::value::ERROR < _limit_level)
            {
                return;
            } // 没有达到输出等级
            // 2.对fmt格式化字符串和不定参进行字符串组织,得到日志消息的字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == -1)
            {
                std::cout << "vasprintf failed!!\n";
                return;
            }
            va_end(ap); // 将ap指针置空
            // 代码一样,可以封装成一个函数
            serialize(loglevel::value::ERROR, file, line, res);
            free(res); // 将指针释放掉
        }
        void fatal(const std::string &file, size_t line, const std::string &fmt, ...)
        { // 日志的输出操作
            // 1.判断当前的日志是否达到输出等级
            if (loglevel::value::FATAL < _limit_level)
            {
                return;
            } // 没有达到输出等级
            // 2.对fmt格式化字符串和不定参进行字符串组织,得到日志消息的字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == -1)
            {
                std::cout << "vasprintf failed!!\n";
                return;
            }
            va_end(ap); // 将ap指针置空
            // 代码一样,可以封装成一个函数
            serialize(loglevel::value::FATAL, file, line, res);
            free(res); // 将指针释放掉
        }
        void serialize(loglevel::value level, const std::string &file, size_t line, char *str)
        {
            // 3.构造LogMsg对象
            LogMsg msg(level, line, file, _logger_name, str); // 传入等级、行号、文件、日志器、有效信息
            // 4.通过格式化工具对LogMsg进行格式化,得到格式化后的日志字符串
            std::stringstream ss;
            _formatter->format(ss, msg);
            // 5.进行日志落地
            log(ss.str().c_str(), ss.str().size()); // 日志字符串和长度
        }

        // 日志落地,抽象接口完成实际的落地输出——不同的日志器会有不同的实际落地方式
        virtual void log(const char *data, size_t len) = 0;

    protected:
        std::mutex _mutex;                         // 互斥锁
        std::string _logger_name;                  // 日志器的名字
        std::atomic<loglevel::value> _limit_level; // 限制日志等级,atomic原子操作的意思是该操作执行过程中不能被中断,该操作要么不执行,要么全部执行,不存在执行一部分的情况。
        Formatter::ptr _formatter;                 // 控制格式化模块的对象
        std::vector<LogSink::ptr> _sliks;          // 这是一个数组,数组里存放日志落地方式的对象
    };
    // 派生出同步日志器
    class SyncLogger : public Logger
    {
     public:
        // 构造函数
        SyncLogger(const std::string &logger_name, loglevel::value level, Formatter::ptr &formatter, std::vector<LogSink::ptr> &sinks) : Logger(logger_name, level, formatter, sinks) {}

    protected:
        // 重写虚函数,同步日志器是将日志通过落地模块句柄进行日志落地
        void log(const char *data, size_t len)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            // 是空
            if (_sliks.empty())
            {
                return;
            }
            // 不是空
            for (auto &sink : _sliks)
            {
                sink->log(data, len);
            }
        }
    };
}
#endif

完成了日志器模块抽象基类的接口实现以及日志器的实际落地功能
3. 对同步日志器进行测试

测试各个接口是否可以编译成功

// 测试代码
#include "util.hpp"
#include "level.hpp"
#include "message.hpp"
#include "format.hpp"
#include "sink.hpp"
#include "logger.hpp"
#include <unistd.h>
int main()
{

    // 日志器模块:同步日志器
    std::string logger_name = "sync_logger";
    logslearn::loglevel::value limit = logslearn::loglevel::value::WARN;
    logslearn::Formatter::ptr fmt(new logslearn::Formatter("[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n"));
    logslearn::LogSink::ptr stdout_lsp = logslearn::SinkFactory::create<logslearn::StdoutSink>();                                 // 标准输出落地
    logslearn::LogSink::ptr file_lsp = logslearn::SinkFactory::create<logslearn::FileSink>("./logfile/test.log");                 // 文件落地方式
    logslearn::LogSink::ptr roll_lsp = logslearn::SinkFactory::create<logslearn::RoolBySizeSink>("./logfile/roll-", 1024 * 1024); // 滚动文件落地方式
    std::vector<logslearn::LogSink::ptr> sinks = {stdout_lsp, file_lsp, roll_lsp};
    logslearn::Logger::ptr logger(new logslearn::SyncLogger(logger_name, limit, fmt, sinks));

    logger->debug(__FILE__, __LINE__, "%s", "测试日志");
    logger->info(__FILE__, __LINE__, "%s", "测试日志");
    logger->warn(__FILE__, __LINE__, "%s", "测试日志");
    logger->error(__FILE__, __LINE__, "%s", "测试日志");
    logger->fatal(__FILE__, __LINE__, "%s", "测试日志");

    size_t cursize = 0;
    size_t count = 0;
    while (cursize < 1024 * 1024 * 10)
    {
        logger->fatal(__FILE__, __LINE__, "测试日志-%d", count++);
        cursize += 20;
    }

    return 0;
}

测试结果符合要求(1024102410/20)
在这里插入图片描述

在这里插入图片描述

1.1.1 局部日志器建造者模式设计

  1. 局部日志器建造者模式的设计和框架分析。(同步日志器模块)
    enum class LoggerType
    {
        LOGGER_SYNC, // 同步日志器
        LOGGER_ASYNC // 异步日志器

    };
    // 使用建造者模式来构造日志器,而不要用户直接去构造日志器,简化了用户的使用复杂度
    // 1.抽象一个日志器建造者类
    //   1.设置日志器类型
    //   2.不同类型的日志器的创建放到同一个日志器建造者类中完成。
    class LoggerBuilder
    {
    public:
        void buildLoggerType(LoggerType type);
        void buildLoggerName(std::string &name);
        void buildLoggerLevel(loglevel::value level);
        // 构造一个格式化器
        void buildLoggerFormatter(const std::string &pattern);
        // 一个日志器可以有多个不同的落地方式
        template <typename SinkType, typename... Args>
        void buildSink(Args &&...arg);

        // 完成我们的日志器构建
        virtual void build() = 0;

    private:
        // 需要管理的数据,也就是建造的零部件
        LoggerType logger_type;       // 日志器类型
        std::string _logger_name;     // 日志器的名字
        loglevel::value _limit_level; // 限制日志等级,atomic原子操作的意思是该操作执行过程中不能被中断,该操作要么不执行,要么全部执行,不存在执行一部分的情况。
        Formatter::ptr _formatter;    // 控制格式化模块的对象
        std::vector<LogSink::ptr> _sliks;
    };
    // 2.派生出具体的建造者类——局部日志器的建造者 & 全局日志器建造者(后边添加了全局单例管理之后,将日志器添加全局管理)
    // 局部日志器的建造者
    class LocalLoggerBuilder : public LoggerBuilder
    {
    public:
        void build() override;
    };
  1. 实现具体操作的各个接口。
    enum class LoggerType
    {
        LOGGER_SYNC, // 同步日志器
        LOGGER_ASYNC // 异步日志器

    };
    // 使用建造者模式来构造日志器,而不要用户直接去构造日志器,简化了用户的使用复杂度
    // 1.抽象一个日志器建造者类
    //   1.设置日志器类型
    //   2.不同类型的日志器的创建放到同一个日志器建造者类中完成。
    class LoggerBuilder
    {
    public:
        // 构造函数
        LoggerBuilder() : logger_type(LoggerType::LOGGER_SYNC),
                          _limit_level(logslearn::loglevel::value::DEBUG) {}

        void buildLoggerType(LoggerType type)
        {
            logger_type = type;
        }
        void buildLoggerName(const std::string &name)
        {
            _logger_name = name;
        }
        void buildLoggerLevel(loglevel::value level)
        {
            _limit_level = level;
        }
        // 构造一个格式化器
        void buildLoggerFormatter(const std::string &pattern)
        {
            _formatter = std::make_shared<Formatter>(pattern);
        }
        // 一个日志器可以有多个不同的落地方式
        template <typename SinkType, typename... Args>
        void buildSink(Args &&...arg)
        {
            LogSink::ptr psink = SinkFactory::create<SinkType>(std::forward<Args>(arg)...); // 完美转发
            _sliks.push_back(psink);
        }

        // 完成我们的日志器构建
        virtual Logger::ptr build() = 0;

    protected:
        // 需要管理的数据,也就是建造的零部件
        LoggerType logger_type;       // 日志器类型
        std::string _logger_name;     // 日志器的名字
        loglevel::value _limit_level; // 限制日志等级,atomic原子操作的意思是该操作执行过程中不能被中断,该操作要么不执行,要么全部执行,不存在执行一部分的情况。
        Formatter::ptr _formatter;    // 控制格式化模块的对象
        std::vector<LogSink::ptr> _sliks;
    };
    // 2.派生出具体的建造者类——局部日志器的建造者 & 全局日志器建造者(后边添加了全局单例管理之后,将日志器添加全局管理)
    // 局部日志器的建造者
    class LocalLoggerBuilder : public LoggerBuilder
    {
    public:
        Logger::ptr build() override
        {
            // 必须要有日志器名称
            assert(_logger_name.empty()==false);
            // 必须要有formatter//必须要有格式化器,没有就要创建
            if (_formatter.get() == nullptr)
            {
                _formatter = std::make_shared<Formatter>();
            }
            // 如果没有落地方式就给它添加一个标准输出的默认落地方式
            if (_sliks.empty())
            {
                buildSink<StdoutSink>();
            }
            // 如果类型为LOGGER_ASYNC,那么日志器为异步日志器
            if (logger_type == LoggerType::LOGGER_ASYNC)
            {
                // 目前不做处理,后面在写
            }
            // 返回同步日志器的对象
            return std::make_shared<SyncLogger>(_logger_name, _limit_level, _formatter, _sliks); // r日志器名字,等级,格式化,落地方式
        }
    };

1.1.2 同步日志器基本功能测试

我们这个建造者模式没有指挥者。因为我们构造对象的零部件没有顺序的要求,只要构造就可以了,所有只有建造者。

// 测试代码
#include "util.hpp"
#include "level.hpp"
#include "message.hpp"
#include "format.hpp"
#include "sink.hpp"
#include "logger.hpp"
#include <unistd.h>
int main()
{
    //同步日志器建造者模式的测试
    //先要构造一个建造者出来
    std::unique_ptr<logslearn::LoggerBuilder> builder(new logslearn::LocalLoggerBuilder());
    //建造者构建零部件
    builder->buildLoggerType(logslearn::LoggerType::LOGGER_SYNC);
    builder->buildLoggerName("sync_logger");
    builder->buildLoggerLevel(logslearn::loglevel::value::WARN);
    builder->buildLoggerFormatter("[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n");
    builder->buildSink<logslearn::StdoutSink>();                                 // 标准输出落地
    builder->buildSink<logslearn::FileSink>("./logfile/test.log"); // 文件落地方式
    builder->buildSink<logslearn::RoolBySizeSink>("./logfile/roll-", 1024 * 1024); // 滚动文件落地方式
    //零部件构建好后,用建造者建筑对象
    logslearn::Logger::ptr logger=builder->build();
    //测试日志打印
    logger->debug(__FILE__, __LINE__, "%s", "测试日志");
    logger->info(__FILE__, __LINE__, "%s", "测试日志");
    logger->warn(__FILE__, __LINE__, "%s", "测试日志");
    logger->error(__FILE__, __LINE__, "%s", "测试日志");
    logger->fatal(__FILE__, __LINE__, "%s", "测试日志");

    size_t cursize = 0;
    size_t count = 0;
    while (cursize < 1024 * 1024 * 10)
    {
        logger->fatal(__FILE__, __LINE__, "测试日志-%d", count++);
        cursize += 20;
    }

    return 0;
}

总结:同步日志器直接将日志消息进行格式化写入文件。

1.2 异步日志器模块设计

在这里插入图片描述
思想:为了避免写日志的过程中阻塞,导致影响业务线程的执行效率。异步的思想就是不让业务线程进行日志的实际落地,而是将日志消息放到缓冲区(一块指定的内存)中接下来有一个专门的异步线程,去针对缓冲区中的数据进行处理(实际的落地操作)
实现
1.实现一个线程安全的缓冲区
2.创建一个异步工作线程,专门用来负责缓冲区中日志信息的落地操作。

缓冲区详情设计
1.使用队列缓存日志消息,逐条处理
要求:不能涉及空间的频繁申请与释放,否则会降低效率。
结果:设计一个环形队列(提前将空间申请好,然后对空间循环利用)
存在问题:这个缓冲区的操作会涉及到多线程,因此缓冲区的操作必须保证线程安全。
线程安全实现:对于缓冲区的读写加锁
因此写日志操作,在实际开发中,不好分配太多资源,工作线程只需要一个日志器就行
涉及到的锁冲突:生产者与生产者之间的互斥&生产者与消费者的互斥。

问题:锁冲突较为严重,所有线程之间都存在互斥关系
解决方案:双缓冲区

在这里插入图片描述
避免了空间频繁的申请与释放,释放与申请的频率,减少了生产者和消费者之间的锁冲突,提高了效率。

1.2.1 单缓冲区设计

设计一个缓冲区:直接存放格式化后的日志消息字符串
好处:
1.减少了LogMsg对象频繁的构造的消耗
2.可以针对缓冲区中的日志消息,一次性进行I0操作,减少I0次数,提高效率
缓冲区类的设计:

1.管理一个存放字符串数据的缓冲区(使用vecotor进行空间管理)
2.当前的写入数据位置的指针(指向可写区域的起始位置,避免数据的写入覆盖)
3.当前的读取数据位置的指针(指向可读数据区域的起始位置,当读取指针与写入指针指向相同位置表示数据取完了)

提供的操作:

1.向缓冲区写入数据
2.获取可读数据起始地址的接口
3.获取可读数据长度的接口
4.移动读写位置的接口
5.初始化缓冲区的操作(将读写位置初始化——将一个缓冲区所有数据处理完毕之后)
6.提供交换缓冲区的接口(交换空间地址,并不交换空间数据)

  1. 第一步,先设计类,把类的框架搭建出来
/*实现异步日志缓冲区*/
#include "util.hpp"
#include <vector>
namespace logslearn
{
// 定义宏,表示缓冲区的大小
#define DEFAULT_BUFFER_SIZE (1 * 1024 * 1024)
#define THRESHOLD_BUFFER_SIZE (8 * 1024 * 1024)
#define INCREMENT_BUFFER_SIZE (1 * 1024 * 1024)
    // 异步缓冲区
    class Buffer
    {
    public:
        // 构造函数
        Buffer() {}
        // 1.向缓冲区写入数据
        void push(const char *data, size_t len);
        // 2.返回可读数据起始地址的接口
        const char *begin();
        // 3.返回可读数据的长度的接口;返回可写数据的长度的接口
        size_t readAbleSize();
        size_t writeAbleSize();
        // 4.移动读写指针进行向后偏移的接口
        void moveWriter(size_t len);
        void moveReader(size_t len);
        // 5.重置读写位置,初始化缓冲区的操作
        void reset();
        // 6.交换缓冲区的接口
        void swap( Buffer &buffer);
        // 判断缓冲区是否为空
        bool empty();

    private:
        // 1.存放字符串数据的缓冲区
        std::vector<char> _buffer;
        // 2.当前可写数据的指针--本质是下标
        size_t _reader_idx;
        // 3.当前可读数据的指针
        size_t _writer_idx;
    };
}
  1. 实现类的各个功能
#ifndef __M_BUFFER_H__
#define __M_BUFFER_H__
/*实现异步日志缓冲区*/

#include "util.hpp"
#include <vector>
#include <cassert>
#include <algorithm>
namespace logslearn
{
// 定义宏,表示缓冲区的大小
#define DEFAULT_BUFFER_SIZE (1 * 1024 * 1024)
#define THRESHOLD_BUFFER_SIZE (8 * 1024 * 1024)
#define INCREMENT_BUFFER_SIZE (1 * 1024 * 1024)
    // 异步缓冲区
    class Buffer
    {
    public:
        // 构造函数
        Buffer() : _buffer(DEFAULT_BUFFER_SIZE), _reader_idx(0), _writer_idx(0) {}
        // 1.向缓冲区写入数据,容量不够就扩容(两种方式,极限测试的时候使用扩容,实际使用过程中固定空间大小,空间不够阻塞)
        void push(const char *data, size_t len)
        {
            // 缓冲区剩余空间不够的情况下:扩容。
            // // 1.固定大小,直接返回
            // if (len > writeAbleSize())
            //     return;
            // 2.动态空间,用于极限测试--扩容
            ensureEnoughSize(len);

            // 2.将数据拷贝到缓冲区
            std::copy(data, data + len, &_buffer[_writer_idx]);
            // 3.将当前写入位置向后偏移
            moveWriter(len);
        }
        // 2.返回可读数据起始地址的接口
        const char *begin()
        {
            return &_buffer[_reader_idx];
        }
        // 3.返回可读数据的长度的接口;返回可写数据的长度的接口
        size_t readAbleSize()
        {
            // 返回可读数据的长度
            // 因为当前实现的缓冲区设计思想是双缓冲区,处理完就交换,所以不存在空间循环使用
            return (_writer_idx - _reader_idx);
        }
        size_t writeAbleSize()
        {
            // 对于扩容的思路来说,不存在可写空间大小,因为总是可写的。
            // 因此这个接口只提供给固定大小缓冲区。
            return (_buffer.size() - _writer_idx);
        }
        // 4.移动读写指针进行向后偏移的接口
        void moveWriter(size_t len)
        {
            // 移动可写指针
            assert((_writer_idx + len) <= _buffer.size());
            _writer_idx += len;
        }
        void moveReader(size_t len)
        {
            // 移动可读指针
            assert(len <= readAbleSize()); // len大就报错
            _reader_idx += len;
        }
        // 5.重置读写位置,初始化缓冲区的操作
        void reset()
        {
            // 读写位置都为零
            _writer_idx = 0; // 缓冲区所有空间都是空闲得
            _reader_idx = 0; //_reader_idx与_writer_idx相等就表示没有数据可以读
        }
        // 6.交换缓冲区的接口
        void swap(Buffer &buffer)
        {
            // 交换缓冲区地址
            _buffer.swap(buffer._buffer);
            // 读写位置的地址也要交换
            std::swap(_reader_idx, buffer._reader_idx);
            std::swap(_writer_idx, buffer._writer_idx);
        }
        // 判断缓冲区是否为空
        bool empty()
        {
            return (_reader_idx == _writer_idx); // 位置一样就是空
        }

    private:
        // 对空间进行扩容
        void ensureEnoughSize(size_t len)
        {
            // 不需要扩容
            if (len <= writeAbleSize())
                return;
            size_t new_size = 0;
            if (_buffer.size() < THRESHOLD_BUFFER_SIZE)
            {
                // 小于阈值则翻倍增长
                new_size = _buffer.size() * 2;
            }
            else
            {
                new_size = _buffer.size() + INCREMENT_BUFFER_SIZE+len; // 否则线性增长
            }
            // 重新调整空间大小
            _buffer.resize(new_size);
        }

    private:
        // 1.存放字符串数据的缓冲区
        std::vector<char> _buffer;
        // 2.当前可写数据的指针--本质是下标
        size_t _reader_idx;
        // 3.当前可读数据的指针
        size_t _writer_idx;
    };
}
#endif
  1. 对异步缓冲区进行代码完善和功能测试

测试问题:读取文件数据,一点一点写入缓冲区,最终将缓冲区数据写入文件,判断生成的新文件

// 测试代码
#include "util.hpp"
#include "level.hpp"
#include "message.hpp"
#include "format.hpp"
#include "sink.hpp"
#include "logger.hpp"
#include <unistd.h>
#include "buffer.hpp"
#include <fstream>


int main()
{
   
    // 异步日志器缓冲区测试
    // 读取文件数据,一点一点写入缓冲区,最终将缓冲区数据写入文件,判断生成的新文件与源文件是否一致
    std::ifstream ifs("./logfile/test.log", std::ios::binary); // 打开一个文件
    if (ifs.is_open() == false)
    {
        return -1;
    } // 文件打开失败返回-1
    // 让读写位置跳转到末尾
    ifs.seekg(0, std::ios::end);
    // 获取当前读写位置相对于起始位置的偏移量
    size_t fsize = ifs.tellg();
    // 重新让指针跳转到起始位置
    ifs.seekg(0, std::ios::beg);
    std::string body;
    body.resize(fsize);
    ifs.read(&body[0], fsize);
    if (ifs.good() == false)
    {
        std::cout << "read error!\n";
        return -1;
    }
    // 打开文件,就要关闭文件
    ifs.close();
    std::cout << fsize << std::endl; // 读取文件的数据大小
    logslearn::Buffer buffer;        // 定义一个缓冲区
    for (int i = 0; i < body.size(); i++)
    {
        buffer.push(&body[i], 1);
    }
    std::cout << buffer.readAbleSize() << std::endl; // buffer里面可读的数据大小

    std::ofstream ofs("./logfile/tem.log", std::ios::binary);
    // //一次性写入
    // ofs.write(buffer.begin(),buffer.readAbleSize());
    //错误
    // for (int i = 0; i < buffer.readAbleSize(); i++)
    // { // readAbleSize()可读数据的长度,可读数据在减少,i在++,双休套进的过程
    //     ofs.write(buffer.begin(), 1);
    //     if (ofs.good() == false)
    //     {
    //         std::cout << "writer error!\n";
    //         return -1;
    //     }
    //     buffer.moveReader(1);
    // }
    //正确
    size_t rsize=buffer.readAbleSize();
      for (int i = 0; i < rsize; i++)
    { // readAbleSize()可读数据的长度,可读数据在减少,i在++,双休套进的过程
        ofs.write(buffer.begin(), 1);
        if (ofs.good() == false)
        {
            std::cout << "writer error!\n";
            return -1;
        }
        buffer.moveReader(1);
    }
    ofs.close(); // 关闭文件

    return 0;
}

在这里插入图片描述
md5sum 可以验证文件的完整性,tem.log和test.log文件的md5值一样,说明文件内容一模一样

1.2.2 异步工作线程的设计(双缓冲区思想)

异步工作器:
1.异步工作使用双缓冲区的思想
外界将任务数据,添加到输入缓冲区中
异步线程对处理缓冲区中的数据进行处理,若处理缓冲区中没有数据了则交换缓冲区
实现:
管理的成员

1.双缓冲区(生产,消费)
2.互斥锁 // 保证线程安全
3.条件变量-生产&消费(生产缓冲区没有数据,处理完消费缓冲区数据后就休眠)
4.回调函数(针对缓冲区中数据的处理接口-外界传入一个函数,告诉异步工作器数据该如何处理)

提供的操作:

1.停止异步工作器
2.添加数据到缓冲区

私有操作:

创建线程,线程入口函数中,交换缓冲区,对消费缓冲区数据使用回调函数进行处理,处理完后再次交换

  1. 设计异步工作器的框架,已经具体成员
/*
    实现异步工作器
*/
#ifndef __M_LOOPER_H__
#define __M_LOOPER_H__
#include "buffer.hpp"         //缓冲区
#include <thread>             //线程库
#include <mutex>              //互斥锁
#include <condition_variable> //条件变量
#include <functional>         //包装器
#include <atomic>             //原子类型
namespace logslearn
{
    // 异步工作器类
    using Functor = std::function<void(Buffer &)>;
    class AsyncLooper
    {
    public:
        using ptr = std::shared_ptr<AsyncLooper>;
        AsyncLooper();
        void stop();
        void push(const char *data, size_t len);

    private:
        void threadEntry(); // 线程入口函数
    private:
        Functor _callBack; // 具体对缓冲区数据进行处理的回调函数,由异步工作器使用者传入。
    private:
        // bool _stop;//工作器停止标准,如果工作器停止了,会存在线程安全问题
        std::atomic<bool> _stop;           // 让工作器停止标准变成原子性操作,提高了线程安全
        Buffer _pro_buf;                   // 生产缓冲区
        Buffer _con_buf;                   // 消费缓冲区
        std::mutex _mutex;                 // 互斥锁
        std::condition_variable _cond_pro; // 两个pcb的等待队列,这是生产者,等待队列的条件变量
        std::condition_variable _cond_con; // 这是消费者,等待队列的条件变量
        std::thread _thread;               // 异步工作器对应的工作线程
    };
}

#endif
  1. 异步工作器的具体实现
/*
    实现异步工作器
*/
#ifndef __M_LOOPER_H__
#define __M_LOOPER_H__
#include "buffer.hpp"         //缓冲区
#include <thread>             //线程库
#include <mutex>              //互斥锁
#include <condition_variable> //条件变量
#include <functional>         //包装器
#include <atomic>             //原子类型
namespace logslearn
{
    // 异步工作器类
    using Functor = std::function<void(Buffer &)>;
    enum class AsyncType
    {
        ASYNC_SAFE,  // 安全状态,表示缓冲区满了就阻塞,避免了资源耗尽的风险
        ASUNC_UNSAFE // 非安全状态,不考虑资源耗尽的情况,可以无限扩容,常用与测试
    };

    class AsyncLooper
    {
    public:
        using ptr = std::shared_ptr<AsyncLooper>;
        AsyncLooper(const Functor &cb, AsyncType looper_type = AsyncType::ASUNC_UNSAFE) : _looper_type(looper_type), _stop(false), _thread(std::thread(&AsyncLooper::threadEntry, this)),
                                                                                          _callBack(cb) {}
        ~AsyncLooper() { stop(); }
        void stop()
        {
            _stop = true;           // 将退出标志设置为true
            _cond_con.notify_all(); // 唤醒所有的工作线程,工作线程只有一个
            _thread.join();//等待工作线程退出
        }
        void push(const char *data, size_t len)
        {
            // 1.无限扩容-非安全(极限压力测试的情况下使用)2.固定大小
            // 加个互斥锁
            std::unique_lock<std::mutex> lock(_mutex);
            // 条件变量空值,若缓冲区剩余空间大小大于数据长度,就可以添加数据
            // 如果是安全状态就把这个代码加上,非安全状态就把这个代码屏蔽
            if (_looper_type == AsyncType::ASYNC_SAFE)
                _cond_pro.wait(lock, [&]()
                               { return _pro_buf.writeAbleSize() >= len; });
            // 能够走下来代表满足了条件,可以向缓冲区添加数据
            _pro_buf.push(data, len);
            // 唤醒一个消费者对缓冲区中的数据进行处理
            _cond_con.notify_one();
        }

    private:
        // 线程入口函数--对消费缓冲区中的数据进行处理,处理完毕后,初始化缓冲区,交换缓冲区
        void threadEntry()
        {
            while (1)
            {
                // 要为互斥锁设置一个生命周期,将缓冲区交换完毕后就解锁(并不对数据的处理过程加锁保护)
                {
                    // 1.判断生产缓冲区里有没有数据,有则交换,无则阻塞
                    std::unique_lock<std::mutex> lock(_mutex);
                     //退出标志被设置,且生产缓冲区无数据,这时候在退出,否则有可能会造成生产缓冲区有数据,但是没有被完全处理                   
                    if (_stop&&_pro_buf.empty())
                    break;
                    // 若退出前被唤醒,或者有数据被唤醒,则返回真,继续向下运行,否则重新陷入休眠
                    _cond_con.wait(lock, [&]()
                    { return !_pro_buf.empty() || _stop; }); //_stop是真表示程序退出,把剩余的数据进行交换

                        // 等待完毕,消费者与生产者进行地址交换
                        _con_buf.swap(_pro_buf);
                    // 2.唤醒生产者
                    if (_looper_type == AsyncType::ASYNC_SAFE)
                        _cond_pro.notify_all();
                }
                // 3.被唤醒后,对消费缓冲区进行数据处理
                _callBack(_con_buf);
                // 4.初始化消费缓冲区
                _con_buf.reset();
            }
        }
    private:
        Functor _callBack; // 具体对缓冲区数据进行处理的回调函数,由异步工作器使用者传入。
    private:
        // bool _stop;//工作器停止标准,如果工作器停止了,会存在线程安全问题
        AsyncType _looper_type;            // 默认是安全模式
        std::atomic<bool> _stop;           // 让工作器停止标准变成原子性操作,提高了线程安全
        Buffer _pro_buf;                   // 生产缓冲区
        Buffer _con_buf;                   // 消费缓冲区
        std::mutex _mutex;                 // 互斥锁
        std::condition_variable _cond_pro; // 两个pcb的等待队列,这是生产者,等待队列的条件变量
        std::condition_variable _cond_con; // 这是消费者,等待队列的条件变量
        std::thread _thread;               // 异步工作器对应的工作线程
    };
}
#endif

测试的话,需要异步日志器设计好在测试

1.2.3 异步日志器设计

1.继承于Logger日志器类 对于写日志操作进行函数重写(不再将数据写入文件,而是通过异步消息处理器,放到缓冲区中)
2.通过异步消息处理器,进行日志数据的实际落地
管理的成员:

1.异步工作器(异步消息处理器)
完成后,完善日志器建造者,进行异步日志器安全模式的选择,提供异步日志器的创建

  1. 异步日志器的框架设计
 //派生出异步日志器
    class AsyncLogger:public Logger{
        public:
        AsyncLogger( const std::string &logger_name, loglevel::value level, Formatter::ptr &formatter, std::vector<LogSink::ptr> &sinks,AsyncType looper_type) :
         Logger(logger_name, level, formatter, sinks) {}
            //将数据写入缓冲区
            void log(const char*data,size_t len);
            //设计一个实际落地函数(将缓冲区里的数据进行落地)
            void realLog(Buffer &buf);
        private:
           AsyncLooper::ptr _looper;
    };

  1. 完成异步日志器类的功能
    //派生出异步日志器
    class AsyncLogger:public Logger{
        public:
        AsyncLogger( const std::string &logger_name, loglevel::value level, Formatter::ptr &formatter, std::vector<LogSink::ptr> &sinks,AsyncType looper_type) :
         Logger(logger_name, level, formatter, sinks),_looper(std::make_shared<AsyncLooper>(std::bind(&AsyncLogger::realLog,this,std::placeholders::_1),looper_type)) {}
            //将数据写入缓冲区
            void log(const char*data,size_t len){
                //消息入队操作,不需要加锁,已经是线程安全得了
                _looper->push(data,len);
            }
            //设计一个实际落地函数(将缓冲区里的数据进行落地)
            void realLog(Buffer &buf){
                if(_sliks.empty())return ;
                for(auto &sink:_sliks){
                    sink->log(buf.begin(),buf.readAbleSize());
                }
            }
        private:
           AsyncLooper::ptr _looper;
    };

1.2.4 异步日志器建造者模式设计

1.本地局部的日志器建造者——异步日志器模块

  1. 局部日志器建造者功能的完善
    这个类的完善,需要在异步日志器完成之后,才能完善
    完善部分:

1.首先要异步日志器完成
2.建造者需要再加一个默认模式的零件,以及零部件的建造
3.完善构造函数
4.异步日志器对象的实例化

    enum class LoggerType
    {
        LOGGER_SYNC, // 同步日志器
        LOGGER_ASYNC // 异步日志器

    };
    // 使用建造者模式来构造日志器,而不要用户直接去构造日志器,简化了用户的使用复杂度
    // 1.抽象一个日志器建造者类
    //   1.设置日志器类型
    //   2.不同类型的日志器的创建放到同一个日志器建造者类中完成。
    class LoggerBuilder
    {
    public:
        // 构造函数
        LoggerBuilder() : logger_type(LoggerType::LOGGER_SYNC),
                          _limit_level(logslearn::loglevel::value::DEBUG),
                          _looper_type(AsyncType::ASYNC_SAFE) {}
        void buildEnabeUnSafeAsync()
        {
            _looper_type = AsyncType::ASUNC_UNSAFE; // 非安全模式
        }
        void buildLoggerType(LoggerType type)
        {
            logger_type = type;
        }
        void buildLoggerName(const std::string &name)
        {
            _logger_name = name;
        }
        void buildLoggerLevel(loglevel::value level)
        {
            _limit_level = level;
        }
        // 构造一个格式化器
        void buildLoggerFormatter(const std::string &pattern)
        {
            _formatter = std::make_shared<Formatter>(pattern);
        }
        // 一个日志器可以有多个不同的落地方式
        template <typename SinkType, typename... Args>
        void buildSink(Args &&...arg)
        {
            LogSink::ptr psink = SinkFactory::create<SinkType>(std::forward<Args>(arg)...); // 完美转发
            _sliks.push_back(psink);
        }

        // 完成我们的日志器构建
        virtual Logger::ptr build() = 0;

    protected:
        // 需要管理的数据,也就是建造的零部件
        AsyncType _looper_type;       // 异步线程的安全模式
        LoggerType logger_type;       // 日志器类型
        std::string _logger_name;     // 日志器的名字
        loglevel::value _limit_level; // 限制日志等级,atomic原子操作的意思是该操作执行过程中不能被中断,该操作要么不执行,要么全部执行,不存在执行一部分的情况。
        Formatter::ptr _formatter;    // 控制格式化模块的对象
        std::vector<LogSink::ptr> _sliks;
    };
    // 2.派生出具体的建造者类——局部日志器的建造者 & 全局日志器建造者(后边添加了全局单例管理之后,将日志器添加全局管理)
    // 局部日志器的建造者
    class LocalLoggerBuilder : public LoggerBuilder
    {
    public:
        Logger::ptr build() override
        {
            // 必须要有日志器名称
            assert(_logger_name.empty() == false);
            // 必须要有formatter//必须要有格式化器,没有就要创建
            if (_formatter.get() == nullptr)
            {
                _formatter = std::make_shared<Formatter>();
            }
            // 如果没有落地方式就给它添加一个标准输出的默认落地方式
            if (_sliks.empty())
            {
                buildSink<StdoutSink>();
            }
            // 如果类型为LOGGER_ASYNC,那么日志器为异步日志器
            if (logger_type == LoggerType::LOGGER_ASYNC)
            {
                // 返回异步日志器对象
                return std::make_shared<AsyncLogger>(_logger_name, _limit_level, _formatter, _sliks,_looper_type);
            }
            // 返回同步日志器的对象
            return std::make_shared<SyncLogger>(_logger_name, _limit_level, _formatter, _sliks); // r日志器名字,等级,格式化,落地方式
        }
    };

}

1.2.5 异步日志器基本功能测试

异步工作器与异步日志器一起联调。
测试:用建造者模式,构建对象,选择异步日志器,并且把10万条日志打印在文件和屏幕上
set nu//看文件里数据的行号

// 测试代码
#include "util.hpp"
#include "level.hpp"
#include "message.hpp"
#include "format.hpp"
#include "sink.hpp"
#include "logger.hpp"
#include <unistd.h>
#include "buffer.hpp"
#include "looper.hpp"
#include <fstream>
int main()
{
 
    //异步日志器的测试
    //异步日志器和异步工作器进行联调
    // //先要构造一个建造者出来
    std::unique_ptr<logslearn::LoggerBuilder> builder(new logslearn::LocalLoggerBuilder());
    //建造者构建零部件
    builder->buildLoggerType(logslearn::LoggerType::LOGGER_ASYNC);
    builder->buildLoggerName("async_logger");
    builder->buildLoggerLevel(logslearn::loglevel::value::WARN);
    builder->buildLoggerFormatter("[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n");
    builder->buildSink<logslearn::StdoutSink>();                                 // 标准输出落地
    builder->buildSink<logslearn::FileSink>("./logfile/async.log"); // 文件落地方式
    // builder->buildSink<logslearn::RoolBySizeSink>("./logfile/roll-", 1024 * 1024); // 滚动文件落地方式
    //零部件构建好后,用建造者建筑对象
    logslearn::Logger::ptr logger=builder->build();
    //测试日志打印
    logger->debug(__FILE__, __LINE__, "%s", "测试日志");
    logger->info(__FILE__, __LINE__, "%s", "测试日志");
    logger->warn(__FILE__, __LINE__, "%s", "测试日志");
    logger->error(__FILE__, __LINE__, "%s", "测试日志");
    logger->fatal(__FILE__, __LINE__, "%s", "测试日志");
    size_t count = 0;
    while (count < 100000)
    {
        logger->fatal(__FILE__, __LINE__, "测试日志-%d", count++);
    }
    return 0;
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1931075.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

5.串口通信

文章目录 串口的介绍TTLRS-232RS-485 分类方式串口并口同步异步 相关寄存器SCONPCONTMODSBUFIE 中断处理函数代码编写main.cdelay.cdelay.hUart.cUart.hmain.h回环 继电器ESP8266AT指令代码编写main.cdefine.cdefine.hsend.csend.hreceive.cdelay.cdelay.h 串口的介绍 UART&am…

项目发布部署:如何发布.NETCore项目到IIS服务器?

前言&#xff1a;本文将详细介绍如何发布.NET Core项目到IIS服务器。首先&#xff0c;第一步需要安装IIS&#xff0c;介绍了在本地电脑和服务器中进行安装。然后需要安装SDK和运行时才能发布.NETCore项目。其次介绍了如何发布.NETCore项目和Vue项目&#xff0c;并配置IIS。最后…

自适应键盘,自带隐藏键盘的输入框(UITextField)

引言 在iOS开发中&#xff0c;输入框占据着举足轻重的地位。与安卓不同&#xff0c;iOS输入框经常面临键盘遮挡的问题&#xff0c;或者无法方便地取消键盘。为了解决这些问题&#xff0c;有许多针对iOS键盘管理的库&#xff0c;如IQKeyboardManager、TPKeyboardAvoiding和Keyb…

数仓实践:维度建模标准规范定义

一、引言 指以维度建模作为理论基础,构建总线矩阵,划分和定义数据域、业务过程、维度、度量/原子指标、业务限定、时间周期、统计粒度、派生指标。 规范定义如下: 二、名词术语 名词解释数据域面向业务分析,将业务过程或者维度进行抽象的集合。其中,业务过程可以概括为…

idea中使用maven

默认情况下&#xff0c;idea会自动下载并安装maven&#xff0c;这不便于我们管理。 最好是自行下载maven&#xff0c;然后在idea中指定maven的文件夹路径

鸿蒙开发:Universal Keystore Kit(密钥管理服务)【获取密钥属性(C/C++)】

获取密钥属性(C/C) HUKS提供了接口供业务获取指定密钥的相关属性。在获取指定密钥属性前&#xff0c;需要确保已在HUKS中生成或导入持久化存储的密钥。 在CMake脚本中链接相关动态库 target_link_libraries(entry PUBLIC libhuks_ndk.z.so)开发步骤 构造对应参数。 keyAlias&…

华为OD七日集训第1期 - 按算法分类,由易到难,循序渐进,玩转OD

目录 一、适合人群二、本期训练时间三、如何参加四、7日集训第2期五、精心挑选21道高频100分经典题目&#xff0c;作为入门。第1天、逻辑分析第2天、数组第3天、双指针第4天、数据结构第5天、队列第6天、栈第7天、滑动窗口 六、集训总结 大家好&#xff0c;我是哪吒。 最近一直…

Idea如何快速高效的修改项目的包名

文章目录 前言一、全局替换的快捷键二、弹出如下的界面 前言 当我们有时候在做项目迁移的时候&#xff0c;需要快速的修改项目的包名&#xff01;那么如何快速高效的修改项目的报名呢&#xff1f; 经过尝试了很多方法&#xff01;最简单的方法就是利用全局替换来直接替换报名&…

半自动辅助制作数据集【实例分割】

利用yoloV8的实例分割模型&#xff0c;半自动辅助制作数据集 引言&#xff1a;【主要步骤】 步骤1&#xff1a;无人机航拍&#xff0c;收集基础图片 步骤2&#xff1a;将收集到的图片&#xff0c;全部用yoloV8-seg.pt模型进行实例分割【预测之前&#xff0c;将配置文件default.…

图——图的应用02最短路径(Dijkstra算法与Floyd算法详解),拓扑排序及关键路径

前面介绍了图的应用——01最小生成树章节&#xff0c;大家可以通过下面的链接学习&#xff1a; 图——图的应用01最小生成树&#xff08;Prim算法与Kruskal算法详解&#xff09; 今天就讲一下图的其他应用——最短路径&#xff0c;拓扑排序及关键路径。 目录 一&#xff0c…

解决Qt6 OpenCV项目编译出现错误undefined reference to symbol ‘cv::cvtColor,无法解析的符号

在一次Qt6 OpenCV项目编译的过程中&#xff0c;出现错误undefined reference to symbol cv::cvtColor&#xff0c;无法解析的符号&#xff0c;问题是查看INCLUDEPATH和LIBS&#xff0c;都已经添加正确。 而且这个函数也可以在头文件中索引到。到底是什么问题呢&#xff1f; 清…

公司政务办理流程分享(北京)

社保增减员&#xff1a; 参保登记——增减员业务这么办_北京市人力资源和社会保障局_社会保险 https://rsj.beijing.gov.cn/yltc/202310/t20231025_3287007.html 公积金增减员&#xff1a; https://dwwsyw.gjj.beijing.gov.cn/

【从零开始实现stm32无刷电机FOC】【实践】【5/6 stm32 adc外设的高级用法】

目录 采样时刻触发采样同步采样 点击查看本文开源的完整FOC工程 本节介绍的adc外设高级用法用于电机电流控制。 从前面几节可知&#xff0c;电机力矩来自于转子的q轴受磁力&#xff0c;而磁场强度与电流成正比&#xff0c;也就是说电机力矩与q轴电流成正相关&#xff0c;控制了…

which 命令在Linux中是一个快速查找可执行文件位置的工具

文章目录 0、概念1、which --help2、which命令解释 0、概念 which命令用于查找命令的可执行文件的路径which 命令在 Linux 中用于查找可执行命令的完整路径。当你在 shell 中输入一个命令时&#xff0c;shell 会在环境变量 $PATH 定义的目录列表中查找这个命令。which 命令可以…

数据结构——单链表详解(超详细)(2)

前言&#xff1a; 上一篇文章小编简单的介绍了单链表的概念和一些函数的实现&#xff0c;不过为了保证文章的简洁&#xff0c;小编把它分成了两篇来写&#xff0c;这一篇小编紧接上一篇文章继续写单链表函数功能的实现&#xff1a; 目录&#xff1a; 1.单链表剩余函数的编写 1.…

Spring Security Oauth2源码分析

Spring Security Oauth2源码分析 前言一&#xff1a;客户端OAuth2授权请求的入口1、DefaultOAuth2AuthorizationRequestResolver类OAuth2AuthorizationRequest类authorizationRequestUri 的构建机制redirectUri 3、OAuth2AuthorizationRequestRedirectFilter类 二&#xff1a;O…

hiphop音乐风格分类有几种 怎么使用FL Studio制作Hip Hop音乐 hiphop音乐制作教程

Hip Hop音乐是一类新潮的音乐风格&#xff0c;融合了许多不同的文化元素和音乐表达方式。嘻哈&#xff08;hip hop&#xff09;诞生于美国贫民区街头的一种文化形式&#xff0c;一般的说法认为它诞生于美国纽约布朗克斯。嘻哈首先在纽约市北部布朗克斯市区的非裔及拉丁裔青年之…

kali进行host碰撞实验

目录 在kali上安装docker&#xff0c;完成环境搭建&#xff0c;进行host碰撞实验 更新软件包列表 下载阿里云的镜像源 添加 GPG 密钥并添加更新源 安装 Docker 安装成功检验 开启docker 检查docker状态 拉取镜像 在kali访问ip&#xff08;加端口&#xff09; 下载压缩…

京东超级18活动入口!京东超级18活动怎么玩?

京东推出新活动&#xff0c;低价的持续性项目“京东超级18”。 活动入口&#xff1a; 京东超级18&#xff0c;领60元大额补贴&#xff01; 口令直达&#xff1a;14:/京东超级18&#xffe5;NF5fuBPWIIaHRiMr&#xffe5;&#xff0c;↗鯨○Dσσδng。 手机京东搜索&#xf…

Claude 3.5 Sonnet模型发布,对比ChatGPT4o孰强孰弱

Anthropic 这家生而为打击 OpenAI 安全问题的公司&#xff0c;正式发布了Claude 3.5 Sonnet模型&#xff01; 用官网的话就是&#xff1a; 今天&#xff0c;我们推出了 Claude 3.5 Sonnet&#xff0c;这是我们即将推出的 Claude 3.5 型号系列中的第一个版本。Claude 3.5 Sonne…