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


    本节内容主要讲诉了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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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
    • 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

    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;
    }; 
    
    • 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

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

    二、代码调试

    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;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      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)
      
      
      • 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
    2. 下面开始进行调试,我们直接在main函数所在行设置一个断点,执行调试,走到第14行代码语句,调用宏函数打印日志

      image-20231017204921376

    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;
          }
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    4. 继续输入s,进入log.cc文件,执行LoggerManager()的构造函数,默认创建一个主日志器

      image-20231018185650442

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

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

        image-20231018185935096

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

        接着执行logger的成员函数addAppender

        image-20231018190643100

      • 到这里我们就已经通过日志管理器设置了一个主日志,并且初始化了日期器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()
      
      • 1

      在来看这段代码,经过调试是不是一下就清楚了含义,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;                              // 默认日志
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      继续输入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进入函数内部

      image-20231018200643501

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

      image-20231018201538232

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

      image-20231018202128417

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

      image-20231018202439756

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

      image-20231018202531032

  • 相关阅读:
    本科毕业论文研究结果与预期不符怎么办?
    ANR Application Not Responding
    ArcGIS中ArcMap导入mxd地图文档文件出现红色感叹号、地图空白的解决
    嵌入式Linux driver开发实操(二十二):写一个ALSA驱动程序
    Vue中 引入使用 localforage 改进本地离线存储(突破5M限制)
    论文阅读 DyREP:Learning Representations Over Dynamic Graphs
    Win10 ping 虚拟机kali 请求超时解决办法
    用C++写个示例 linux WebAssembly技术支持的js调用串行通信
    Java mail发送邮件时卡住,没有任何异常日志(出现阻塞线程)
    centos获取服务器公网ip
  • 原文地址:https://blog.csdn.net/qq_18824403/article/details/133914112