本节内容主要讲诉了sylar高性能服务器视频P9的内容,并给出了代码逐步调试的步骤和结果。本节内容主要新增了一个配置类,允许重新从用户的给定配置文件中加载用户配置,如果你对于新增代码还有疑惑,看看下面的调试步骤一定会有帮助。
本节视频主要讲解了配置类的声明和定义,允许程序从用户的给定配置文件中加载用户配置。一般配置应该包含下列内容:
下面内容将分为两个部分,第一部分将会解析sylar已经搭建好的一个最基本的配置类,第二部分会使用测试案例逐行调试。
该类包含两个成员变量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; // 配置参数的描述
};
采用了模板的形式,保证可以接收任何类型的配置内容。
目前只有一个成员变量m_val,保存参数值
构造函数调用ConfigvarBase初始化配置名称和配置参数描述,还有参数值。
sylar使用了boost::lexical_cast进行类型转换,之前看C++ primer第五版介绍了三种类型转换方法,不知道和这个有啥区别,但是相比标准库定义的一些类型转换肯定实用得多,比如atoi、atof、itoa等,存在下列缺点:
// 配置参数类
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;
};
创建一个map,key为配置名称,value为配置名称对应的配置参数,提供更便捷的访问配置参数类的方法
成员变量包含一个静态的ConfigVarMap:s_datas,这里要注意当成员变量定义为静态时一定先初始化再进行使用。
其它类中静态成员和函数的注意事项(s_fun,s_val分别是静态函数和静态变量,S表示类,n_fun,n_val分别是普通函数和变量)
第一个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;
};
以上就是本节视频搭建的一个基础配置类,对于其它文中代码中的改动我这里就不列举了,看了视频应该都知道。下面就通过逐步调试理解配置类的一个运行流程。
首先创建一个新的测试文件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)
下面开始进行调试,我们直接在main函数所在行设置一个断点,执行调试,走到第14行代码语句,调用宏函数打印日志
输入s,调用我们之前在singleton.h定义得单例模式设计里面的GetInstance(),创建当前实例
template<class T, class X = void, int N = 0>
class Singleton {
public:
static T* GetInstance() {
static T v;
return &v;
}
};
继续输入s,进入log.cc文件,执行LoggerManager()的构造函数,默认创建一个主日志器
在该构造函数里会做两件事
初始化一个日志:输入s继续查看,进入log.cc中logger的构造函数,之前这一块在第一篇文章我就调试了一次,所以这里就不展现一个日志所以的初始化内容
添加一个默认输出到文件的appender,输入s,如下图,先访问StdoutAppender派生类,然后访问其基类LogAppender,这里就是已经初始化了一个appender类
接着执行logger的成员函数addAppender
到这里我们就已经通过日志管理器设置了一个主日志,并且初始化了日期器logger和输出appender。别忘了我们一直还在singleton.h中的GetInstance方法类,上面的所有东西都是执行static T v;
产生的,之后返回创建好的实例。
初始化工作到这就已经结束,别忘了目前执行的语句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查看代码执行,那么现在就已经获得主日志器
看一个日志消息的输出,里面的所有值我们都需要进行获取,
继续执行,发现代码在获得了主日志器后,会先去获得日志level(因为在宏函数流式输出里面,传入给logevent指针的第二个参数是level,后面获取都是根据这个顺序)
获取线程ID
获取fiberID
logevent的成员变量刚刚已经获取完了,那么进入LogEvent的构造函数进行初始化
将刚刚创建好的logevent放入LogEventWrap
然后因为使用了宏函数SYLAR_LOG_INFO,所以根据函数定义要去调用event的getSS函数
中间省略了之前调试过的如果将日志格式化输出到输出地的步骤
然后释放LogEventWrap,调用它的析构函数,调用logger里面的log方法,按照appender的类型进行日志输出
注意,这个8080是语句g_int_value_config->getValue()
,跟我们进入SYLAR_LOG_INFO(SYLAR_LOG_ROOT());
无关,之前没理解清楚,还重新调试找了好几遍,调试时常用finish命令会加快效率
从上面结果可以看到,调用g_int_value_config,它是一个配置类,名称是system.port,参数值是8080,描述是system port,通过Lookup函数在配置类的map中找到并打印,下面是调试步骤