sylar高性能服务器-配置(P9)代码解析+调试分析

文章目录

    • 一、代码解析
      • 1.1配置基类(ConfigVarBase)
      • 1.2配置参数类(ConfigVar)
      • 1.3配置管理类(Config)
    • 二、代码调试
      • 2.1SYLAR_LOG_INFO(SYLAR_LOG_ROOT())
      • 2.2ConfigVart调试

本节内容主要讲诉了sylar高性能服务器视频P9的内容,并给出了代码逐步调试的步骤和结果。本节内容主要新增了一个配置类,允许重新从用户的给定配置文件中加载用户配置,如果你对于新增代码还有疑惑,看看下面的调试步骤一定会有帮助。

本节视频主要讲解了配置类的声明和定义,允许程序从用户的给定配置文件中加载用户配置。一般配置应该包含下列内容:

  • 名称:唯一字符串,不能与其它配置项冲突
  • 类型:基本类型int,double等,或者自定义类型
  • 默认值:如果用户没有给指定配置项赋值,程序需要赋予默认值
  • 配置更新通知:一旦配置发生变化,需要通知所有使用了这项配置的代码
  • 校验方法:确保用户不会给配置项设置一个非法值

下面内容将分为两个部分,第一部分将会解析sylar已经搭建好的一个最基本的配置类,第二部分会使用测试案例逐行调试。

一、代码解析

1.1配置基类(ConfigVarBase)

该类包含两个成员变量m_name和m_description,分别表示配置参数的名称,后续我们在配置管理类中寻找配置靠的就是这个成员;第二个成员表示配置参数的描述。

这里采用的是虚析构函数,防止资源泄露,如果不定义成虚析构函数,那么当基类指针指向派生类时,由于delete xx只能调用基类的虚构函数,派生类的析构函数无法调用,造成了资源泄露(简单的解释,如果之前没有看过primer或者其它书的虚函数讲解,建议花点事件看下)。

toString()将参数值转换为string类型

fromString()从string类型转换为参数值

class ConfigvarBase {
public:
    typedef std::shared_ptr<ConfigvarBase> ptr;
    ConfigvarBase(const std::string name, const std::string description = "")
        :m_name(name)
        ,m_description(description) {}                                  // 构造函数
    virtual ~ConfigvarBase() {}                                         // 析构函数

    const std::string& getName() const { return m_name; }               // 返回配置参数名称
    const std::string& getDescription() const { return m_description; } // 返回配置参数描述
    virtual std::string toString() = 0;                                 // 转换成字符串
    virtual bool fromString(const std::string& val) = 0;                // 从字符串初始化值
protected:
    std::string m_name;                                                 // 配置参数的名称
    std::string m_description;                                          // 配置参数的描述
};

1.2配置参数类(ConfigVar)

采用了模板的形式,保证可以接收任何类型的配置内容。

目前只有一个成员变量m_val,保存参数值

构造函数调用ConfigvarBase初始化配置名称和配置参数描述,还有参数值。

sylar使用了boost::lexical_cast进行类型转换,之前看C++ primer第五版介绍了三种类型转换方法,不知道和这个有啥区别,但是相比标准库定义的一些类型转换肯定实用得多,比如atoi、atof、itoa等,存在下列缺点:

  • atoi为代表的标准库类型转换方法限制很多,比如只支持单向转换:从文本到内部数据类型;支持的类型范围仅是内置数值类型的子集;类型的范围不能以统一的方式扩展:将字符串转换成复数或有理数
  • strtol也有着上述相同的限制,但是它为转换过程提供了更好的控制,不过这种空没卵用
  • scanf函数提供更强大的控制,但是缺乏安全性和易用性
  • stringstream对I/O与文本任意类型之间的格式和转换提供了大量的控制,但其对于简单的转换反而比较笨拙:引入额外的局部变量失去了插入表达式的便捷性;在表达式中创建stringstream对象作为临时对象
// 配置参数类
template<class T>
class ConfigVar : public ConfigvarBase {
public:
    typedef std::shared_ptr<ConfigVar> ptr;

    ConfigVar(const std::string& name, const T& default_value, const std::string& description = "")
        :ConfigvarBase(name,description)
        ,m_val(default_value) {

        }
    std::string toString() override {	// 将参数值转换为string类型
        try {
            return boost::lexical_cast<std::string>(m_val);
        } catch(std::exception& e) {
            SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ConfigVar::toString exception"
                << e.what() << "convert: " << typeid(m_val).name() << " to string";
        }
        return "";
    }
    bool fromString(const std::string& val) override {	// 从string转换为参数值
        try {
           m_val = boost::lexical_cast<T>(val);
        } catch (std::exception& e) {
            SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ConfigVar::fromString exception "
                << e.what() << " convert: string to " << typeid(m_val).name();
        }
        return false;
    }

    const T getValue() const { return m_val; }
    void setValue(const T& v) { m_val = v; }

private:
    T m_val;
};

1.3配置管理类(Config)

创建一个map,key为配置名称,value为配置名称对应的配置参数,提供更便捷的访问配置参数类的方法

成员变量包含一个静态的ConfigVarMap:s_datas,这里要注意当成员变量定义为静态时一定先初始化再进行使用。

其它类中静态成员和函数的注意事项(s_fun,s_val分别是静态函数和静态变量,S表示类,n_fun,n_val分别是普通函数和变量)

  1. 不能通过类名来调用类的非静态成员函数:比如S:_fun
  2. 类的对象可以使用静态成员函数和非静态成员函数:S s = new S(),s.s_fun(),s.n_fun()都是可以的
  3. 静态成员函数中不能引用非静态成员,这是因为静态成员函数属于整个类,在类实例化对象之前就已经分配空间了,而类的非静态成员必须在类实例化对象后才有内存空间,
  4. 类的非静态成员函数可以调用用静态成员函数
  5. 类的静态成员变量必须先初始化再使用,也就是使用中sylar一开始没有再config.cc中定义s_datas,所以造成了编译报错

第一个Lookup()函数接收三个参数:配置参数名称,参数值以及描述,首先会调用第二个Lookup()函数,传入参数名称在s_datas中进行查找,如果找到直接调用宏函数输出到logger中,否则先判断是否存在异常状态,没有则定义一个配置参数类

第二个Lookup函数就是在map:s_datas中迭代查找,找不到返回空指针,找到则返回一个类型为配置参数类的智能指针

// 配置管理类
class Config {
public:
    typedef std::map<std::string, ConfigvarBase::ptr> ConfigVarMap;

    // 定义如果没有则初始化
    template<class T>
    static typename ConfigVar<T>::ptr Lookup(const std::string& name,
            const T& default_value, const std::string& description = "") {
        auto tmp = Lookup<T>(name);
        if(tmp) {
            SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "Lookup nmae = " << name << " exists";
        }
        // 发现异常
        if(name.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._012345678") != std::string::npos) { // []
            SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lookup name invalid " << name;
            throw std::invalid_argument(name);
        }
        // 无异常则定义
        typename ConfigVar<T>::ptr v(new ConfigVar<T>(name, default_value, description));
        s_datas[name] = v;
        return v;
    }
    // 查找
    template<class T>
    static typename ConfigVar<T>::ptr Lookup(const std::string& name) {
        auto it = s_datas.find(name);
        if(it == s_datas.end()) { // 未找到
            return nullptr;
        }
        return std::dynamic_pointer_cast<ConfigVar<T>>(it->second); // 找到转换成智能指针
    }
private:
    static ConfigVarMap s_datas;
}; 

以上就是本节视频搭建的一个基础配置类,对于其它文中代码中的改动我这里就不列举了,看了视频应该都知道。下面就通过逐步调试理解配置类的一个运行流程。

二、代码调试

2.1SYLAR_LOG_INFO(SYLAR_LOG_ROOT())

  1. 首先创建一个新的测试文件test_config.cc,并且需要在Cmakelists.txt中增加新的依赖,如下:

    #include 
    #include "../sylar/log.h"
    #include "../sylar/util.h"
    #include"../sylar/config.h"
    
    sylar::ConfigVar<int>::ptr g_int_value_config = 
        sylar::Config::Lookup("system.port", (int)8080, "system port");
    
    // sylar::ConfigVar::ptr g_float_value_config = 
    //     sylar::Config::Lookup("system.value", (float)10.2f, "system value");
    
    int main(int argc, char** argv) {
        
        SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << g_int_value_config->getValue(); // SYLAR_LOG_INFO刚刚报错是因为我在定义时getRoot没有加()进行调用
        SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << g_int_value_config->toString();
    
        // SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << g_float_value_config->getValue(); 
        // SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << g_float_value_config->toString();
        return 0;
    }
    
    cmake_minimum_required(VERSION 2.8)
    project(sylar)
    
    set(CMAKE_VERBOSE_MAKEFILE ON) 
    set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -rdynamic -O0 -g -std=c++11 -Wall -Wno-deprecated -Werror -Wno-unused-function")
    
    set(LIB_SRC
        sylar/log.cc
        sylar/util.cc
        sylar/config.cc
        )
    
    add_library(sylar SHARED ${LIB_SRC})
    #add_library(sylar_static STATIC ${LIB_SRC})
    #SET_TARGET_PROPERTIES(sylar_static PROPERTIES OUTPUT_NAME "sylar")
    
    add_executable(test tests/test.cc)
    add_dependencies(test sylar)
    target_link_libraries(test sylar)
    
    
    add_executable(test_config tests/test_config.cc)
    add_dependencies(test_config sylar)
    target_link_libraries(test_config sylar)
    
    
    SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
    SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
    
    
  2. 下面开始进行调试,我们直接在main函数所在行设置一个断点,执行调试,走到第14行代码语句,调用宏函数打印日志

    sylar高性能服务器-配置(P9)代码解析+调试分析_第1张图片

  3. 输入s,调用我们之前在singleton.h定义得单例模式设计里面的GetInstance(),创建当前实例

    template<class T, class X = void, int N = 0>
    class Singleton {
    public:
        static T* GetInstance() {
            static T v;
            return &v;
        }
    };
    
  4. 继续输入s,进入log.cc文件,执行LoggerManager()的构造函数,默认创建一个主日志器

    sylar高性能服务器-配置(P9)代码解析+调试分析_第2张图片

    在该构造函数里会做两件事

    • 初始化一个日志:输入s继续查看,进入log.cc中logger的构造函数,之前这一块在第一篇文章我就调试了一次,所以这里就不展现一个日志所以的初始化内容

      sylar高性能服务器-配置(P9)代码解析+调试分析_第3张图片

    • 添加一个默认输出到文件的appender,输入s,如下图,先访问StdoutAppender派生类,然后访问其基类LogAppender,这里就是已经初始化了一个appender类

      接着执行logger的成员函数addAppender

      sylar高性能服务器-配置(P9)代码解析+调试分析_第4张图片

    • 到这里我们就已经通过日志管理器设置了一个主日志,并且初始化了日期器logger和输出appender。别忘了我们一直还在singleton.h中的GetInstance方法类,上面的所有东西都是执行static T v;产生的,之后返回创建好的实例。

      image-20231018191123482

  5. 初始化工作到这就已经结束,别忘了目前执行的语句SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << g_int_value_config->getValue();,宏函数SYLAR_LOG_ROOT是将我们一个默认日志器进行返回,也就是主日志器(会在我们没有定义任何日志时生成),

    #define SYLAR_LOG_ROOT() sylar::LoggerMgr::GetInstance()->getRoot()
    

    在来看这段代码,经过调试是不是一下就清楚了含义,GetInstance()会创建一个实例,在用户没有创建日志时默认初始化一个主日志器,然后返回一个日志管理器,再来看看日志管理器的函数分布,包含一个获取主日志器的方法,那么理所当然可以通过getRoot()调用(像这里我在看视频的时候完全不知道,因为跟着sylar一直敲代码,他又很少说明,就算隔一天再来写新东西,遇到使用之前的类都不知道是啥,很多有经验的大佬肯定能跟上,如果你也是我这种小白,建议敲一节视频遇到sylar进行调试,在输出结果和他一样后自己再像我一步一步调试一遍,理解起来会轻松很多)。

    class LoggerManager {
    public:
        LoggerManager();
        Logger::ptr getLogger(const std::string& name);
    
        void init();
        Logger::ptr getRoot() const { return  m_root; }
    private:
        std::map<std::string, Logger::ptr> m_loggers;    // 存储日志
        Logger::ptr m_root;                              // 默认日志
    };
    

    继续输入s查看代码执行,那么现在就已经获得主日志器

    image-20231018192107715

    看一个日志消息的输出,里面的所有值我们都需要进行获取,

    image-20231018192340509

    继续执行,发现代码在获得了主日志器后,会先去获得日志level(因为在宏函数流式输出里面,传入给logevent指针的第二个参数是level,后面获取都是根据这个顺序)

    image-20231018192458334

    获取线程ID

    image-20231018192914258

    获取fiberID

    image-20231018192952565

    logevent的成员变量刚刚已经获取完了,那么进入LogEvent的构造函数进行初始化

    image-20231018193208809

    将刚刚创建好的logevent放入LogEventWrap

    image-20231018193318986

    然后因为使用了宏函数SYLAR_LOG_INFO,所以根据函数定义要去调用event的getSS函数

    image-20231018193524451

    中间省略了之前调试过的如果将日志格式化输出到输出地的步骤

    然后释放LogEventWrap,调用它的析构函数,调用logger里面的log方法,按照appender的类型进行日志输出

    image-20231018193751727

    注意,这个8080是语句g_int_value_config->getValue(),跟我们进入SYLAR_LOG_INFO(SYLAR_LOG_ROOT());无关,之前没理解清楚,还重新调试找了好几遍,调试时常用finish命令会加快效率

    image-20231018200023031

2.2ConfigVart调试

从上面结果可以看到,调用g_int_value_config,它是一个配置类,名称是system.port,参数值是8080,描述是system port,通过Lookup函数在配置类的map中找到并打印,下面是调试步骤

  1. 老样子,打断点,输入s进入函数内部

    sylar高性能服务器-配置(P9)代码解析+调试分析_第5张图片

  2. 首先访问Config配置管理类里面的Lookup方法,传入三个参数,下图展示出来了。

    image-20231018201538232

    在s_data这个配置参数类map中查找,进入第二个Lookup函数

    image-20231018202128417

    没有找到,返回一个空指针,然后继续回到第一个lookup函数,根据传入的三个参数创建一个新的配置参数类

    image-20231018202439756

    然后把该配置类存入s_datas中,最后把这个配置类返回给测试案例中的变量g_int_value_config

    image-20231018202531032

你可能感兴趣的:(服务器,服务器,java,数据库)