该模块是从sylar服务器框架中学习的,以下将会对其进行总结以加深对该框架的理解;
========》视频地址《========
========》参考内容《========
索引
【C++模块实现】| 【01】日志系统实现
【C++模块实现】| 【02】日志系统优化
【C++模块实现】| 【03】文件管理模块
【C++模块实现】| 【04】配置模块
【C++模块实现】| 【05】日志模块增加配置模块的功能
【C++模块实现】| 【06】日志模块添加循环覆盖写文件功能
【C++模块实现】| 【07】对于互斥、自旋锁、条件变量、信号量简介及封装
【C++模块实现】| 【08】循环覆盖写内存缓冲区(日志中多线程记录)
【C++模块实现】| 【09】线程模块及线程池的实现
【C++模块实现】| 【10】定时器的实现
【C++模块实现】| 【11】封装Ipv4、Ipv6、unix网络地址
一、配置模块简介
定义、加载系统的配置项,可由用户自定义于文件中,通过YAML加载解析配置内容至系统中;
1.1 具备要素
- 名称:字符串(唯一);
- 类型:基本、复杂、自定义(该模块需要手动将其转换函数进行特化);
- 值:与类型对应的值、且提供默认值;
- 配置变更通知:一旦用户更新了配置值,那么应该通知所有使用了这项配置的代码,以便于进行一些具体的操作,比如重新打开文
件,重新起监听端口等;
- 校验方法:更新配置时会调用校验方法进行校验,以保证用户不会给配置项设置一个非法的值;
1.2 基本功能
- 支持定义/声明配置项,也就是在提供配置名称、类型以及可选的默认值的情况下生成一个可用的配置项。由于一项配置可能在多个
源文件中使用,所以配置模块还应该支持跨文件声明配置项的方法;
- 支持更新配置项的值,配置项刚被定义时可能有一个初始默认值,但用户可能会有新的值来覆盖掉原来的值;
- 支持从预置的途径中加载配置项,一般是配置文件,也可以是命令行参数,或是网络服务器。这里不仅应该支持基本数据类型的加
载,也应该支持复杂数据类型的加载,比如直接从配置文件中加载一个map类型的配置项,或是直接从一个预定格式的配置文件
中加载一个自定义结构体;
- 支持给配置项注册配置变更通知,配置模块应该提供方法让程序知道某项配置被修改了,以便于进行一些操作。比如对于网络服务
器而言,如果服务器端口配置变化了,那程序应该重新起监听端口。这个功能一般是通过注册回调函数来实现的,配置使用方预
先给配置项注册一个配置变更回调函数,配置项发生变化时,触发对应的回调函数以通知调用方。由于一项配置可能在多个地方
引用,所以配置变更回调函数应该是一个数组的形式;
- 支持给配置项设置校验方法,配置项在定义时也可以指定一个校验方法,以保证该项配置不会被设置成一个非法的值,比如对于文
件路径类的配置,可以通过校验方法来确保该路径一定存在;
- 支持导出当前配置;
二、模块设计
2.1 配置基类
配置基类,提供配置项的名称、描述,且名称都统一转为小写;
提供获取配置参数名称getName()、描述信息getDescription()及值的类型的接口getTypeName();
提供将值转为string及string转为值的接口;
/**
* @brief 配置变量的基类
*/
class ConfigVarBase {
public:
typedef std::shared_ptr<ConfigVarBase> ptr;
/**
* @brief 构造函数
* @param[in] name 配置参数名称[0-9a-z_.]
* @param[in] description 配置参数描述
*/
ConfigVarBase(const std::string& name, const std::string& description = "")
:m_name(name)
,m_description(description) {
std::transform(m_name.begin(), m_name.end(), m_name.begin(), ::tolower);
}
/**
* @brief 析构函数
*/
virtual ~ConfigVarBase() {}
/**
* @brief 返回配置参数名称
*/
const std::string& getName() const { return m_name;}
/**
* @brief 返回配置参数的描述
*/
const std::string& getDescription() const { return m_description;}
/**
* @brief 转成字符串
*/
virtual std::string toString() = 0;
/**
* @brief 从字符串初始化值
*/
virtual bool fromString(const std::string& val) = 0;
/**
* @brief 返回配置参数值的类型名称
*/
virtual std::string getTypeName() const = 0;
protected:
/// 配置参数的名称
std::string m_name;
/// 配置参数的描述
std::string m_description;
};
2.2 类型转换仿函数
以下将使用boost中的lexical_cast函数定义于文件boost/lexical_cast.hpp;
- lexical_cast使用统一的接口实现字符串与目标类型之间的转换;
- 相比于atoi()函数类型检测更严格;
- 错误时抛出boost::bad_lexical_cast异常,因此在使用boost::lexical_cast时一定要捕获异常;
========》仿函数的应用以及如何规范要求《========
========》模板函数的使用及模板特化《========
========》YAML用法及简介《========
该配置模块采用的是yml的文件格式;
【string转其他类型】:
- 先加载到YAML中,再遍历转换;
【其他类型转string】:
- 先转换压入到node,在转换成stringstream;
以下展示了vector以及其他类型的转换方式;
/**
* @brief 类型转换模板类(F 源类型, T 目标类型)
*/
template<class F, class T>
class LexicalCast {
public:
/**
* @brief 类型转换
* @param[in] v 源类型值
* @return 返回v转换后的目标类型
* @exception 当类型不可转换时抛出异常
*/
T operator()(const F& v) {
return boost::lexical_cast<T>(v);
}
};
/**
* @brief 类型转换模板类片特化(YAML String 转换成 std::vector<T>)
*/
template<class T>
class LexicalCast<std::string, std::vector<T> > {
public:
std::vector<T> operator()(const std::string& v) {
YAML::Node node = YAML::Load(v);
typename std::vector<T> vec;
std::stringstream ss;
for(size_t i = 0; i < node.size(); ++i) {
ss.str("");
ss << node[i];
vec.push_back(LexicalCast<std::string, T>()(ss.str()));
}
return vec;
}
};
/**
* @brief 类型转换模板类片特化(std::vector<T> 转换成 YAML String)
*/
template<class T>
class LexicalCast<std::vector<T>, std::string> {
public:
std::string operator()(const std::vector<T>& v) {
YAML::Node node(YAML::NodeType::Sequence);
for(auto& i : v) {
node.push_back(YAML::Load(LexicalCast<T, std::string>()(i)));
}
std::stringstream ss;
ss << node;
return ss.str();
}
};
自定义类型的使用
class Person {
public:
Person() {};
std::string m_name;
int m_age = 0;
bool m_sex = 0;
std::string toString() const {
std::stringstream ss;
ss << "[Person name=" << m_name
<< " age=" << m_age
<< " sex=" << m_sex
<< "]";
return ss.str();
}
bool operator==(const Person& oth) const {
return m_name == oth.m_name
&& m_age == oth.m_age
&& m_sex == oth.m_sex;
}
};
namespace sylar {
template<>
class LexicalCast<std::string, Person> {
public:
Person operator()(const std::string& v) {
YAML::Node node = YAML::Load(v);
Person p;
p.m_name = node["name"].as<std::string>();
p.m_age = node["age"].as<int>();
p.m_sex = node["sex"].as<bool>();
return p;
}
};
template<>
class LexicalCast<Person, std::string> {
public:
std::string operator()(const Person& p) {
YAML::Node node;
node["name"] = p.m_name;
node["age"] = p.m_age;
node["sex"] = p.m_sex;
std::stringstream ss;
ss << node;
return ss.str();
}
};
}
sylar::ConfigVar<Person>::ptr g_person =
sylar::Config::Lookup("class.person", Person(), "system person");
2.3 配置类
该类继承了ConfigVarBase,属性上增加了配置项的值以及值的变更回调函数组;
提供toString()将参数值转换成YAML String;
提供fromString()从YAML String 转成参数的值;
对于变更回调函数组,提供addListener()可对其添加回调函数、delListener()删除、clearListener()清空、getListener() 获取
用户可自定义添加变更回调函数,当值更新后做一些其他操作;
========》类模板简介《========
========》类模板、异常处理及执行期类型识别《========
/**
* @brief 配置参数模板子类,保存对应类型的参数值
* @details T 参数的具体类型
* FromStr 从std::string转换成T类型的仿函数
* ToStr 从T转换成std::string的仿函数
* std::string 为YAML格式的字符串
*/
template<class T, class FromStr = LexicalCast<std::string, T>
,class ToStr = LexicalCast<T, std::string> >
class ConfigVar : public ConfigVarBase {
public:
typedef RWMutex RWMutexType;
typedef std::shared_ptr<ConfigVar> ptr;
typedef std::function<void (const T& old_value, const T& new_value)> on_change_cb;
/**
* @brief 通过参数名,参数值,描述构造ConfigVar
* @param[in] name 参数名称有效字符为[0-9a-z_.]
* @param[in] default_value 参数的默认值
* @param[in] description 参数的描述
*/
ConfigVar(const std::string& name
,const T& default_value
,const std::string& description = "")
:ConfigVarBase(name, description)
,m_val(default_value) {
}
/**
* @brief 将参数值转换成YAML String
* @exception 当转换失败抛出异常
*/
std::string toString() override {
try {
//return boost::lexical_cast<std::string>(m_val);
RWMutexType::ReadLock lock(m_mutex);
return ToStr()(m_val);
} catch (std::exception& e) {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ConfigVar::toString exception "
<< e.what() << " convert: " << typeid(T).name() << " to string"
<< " name=" << m_name;
}
return "";
}
/**
* @brief 从YAML String 转成参数的值
* @exception 当转换失败抛出异常
*/
bool fromString(const std::string& val) override {
try {
setValue(FromStr()(val));
} catch (std::exception& e) {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ConfigVar::fromString exception "
<< e.what() << " convert: string to " << typeid(T).name()
<< " name=" << m_name
<< " - " << val;
}
return false;
}
/**
* @brief 获取当前参数的值
*/
const T getValue() {
RWMutexType::ReadLock lock(m_mutex);
return m_val;
}
/**
* @brief 设置当前参数的值
* @details 如果参数的值有发生变化,则通知对应的注册回调函数
*/
void setValue(const T& v) {
{
RWMutexType::ReadLock lock(m_mutex);
if(v == m_val) {
return;
}
for(auto& i : m_cbs) {
i.second(m_val, v);
}
}
RWMutexType::WriteLock lock(m_mutex);
m_val = v;
}
/**
* @brief 返回参数值的类型名称(typeinfo)
*/
std::string getTypeName() const override { return typeid(T).name();}
/**
* @brief 添加变化回调函数
* @return 返回该回调函数对应的唯一id,用于删除回调
*/
uint64_t addListener(on_change_cb cb) {
static uint64_t s_fun_id = 0;
RWMutexType::WriteLock lock(m_mutex);
++s_fun_id;
m_cbs[s_fun_id] = cb;
return s_fun_id;
}
/**
* @brief 删除回调函数
* @param[in] key 回调函数的唯一id
*/
void delListener(uint64_t key) {
RWMutexType::WriteLock lock(m_mutex);
m_cbs.erase(key);
}
/**
* @brief 获取回调函数
* @param[in] key 回调函数的唯一id
* @return 如果存在返回对应的回调函数,否则返回nullptr
*/
on_change_cb getListener(uint64_t key) {
RWMutexType::ReadLock lock(m_mutex);
auto it = m_cbs.find(key);
return it == m_cbs.end() ? nullptr : it->second;
}
/**
* @brief 清理所有的回调函数
*/
void clearListener() {
RWMutexType::WriteLock lock(m_mutex);
m_cbs.clear();
}
private:
RWMutexType m_mutex;
T m_val;
//变更回调函数组, uint64_t key,要求唯一,一般可以用hash
std::map<uint64_t, on_change_cb> m_cbs;
};
2.4 ConfigVar的管理类
该类用于管理ConfigVar,提供Lookup()创建/访问Config的方法;
提供加载配置文件的接口LoadFromYaml(),LoadFromConfDir()可加载当个文件或整个文件夹;
========》静态成员函数《========
========》类的静态成员《========
========》静态成员在执行期会处理哪些事《========
/**
* @brief ConfigVar的管理类
* @details 提供便捷的方法创建/访问ConfigVar
*/
class Config {
public:
typedef std::unordered_map<std::string, ConfigVarBase::ptr> ConfigVarMap;
typedef RWMutex RWMutexType;
/**
* @brief 获取/创建对应参数名的配置参数
* @param[in] name 配置参数名称
* @param[in] default_value 参数默认值
* @param[in] description 参数描述
* @details 获取参数名为name的配置参数,如果存在直接返回
* 如果不存在,创建参数配置并用default_value赋值
* @return 返回对应的配置参数,如果参数名存在但是类型不匹配则返回nullptr
* @exception 如果参数名包含非法字符[^0-9a-z_.] 抛出异常 std::invalid_argument
*/
template<class T>
static typename ConfigVar<T>::ptr Lookup(const std::string& name,
const T& default_value, const std::string& description = "") {
RWMutexType::WriteLock lock(GetMutex());
auto it = GetDatas().find(name);
if(it != GetDatas().end()) {
auto tmp = std::dynamic_pointer_cast<ConfigVar<T> >(it->second);
if(tmp) {
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "Lookup name=" << name << " exists";
return tmp;
} else {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lookup name=" << name << " exists but type not "
<< typeid(T).name() << " real_type=" << it->second->getTypeName()
<< " " << it->second->toString();
return nullptr;
}
}
if(name.find_first_not_of("abcdefghikjlmnopqrstuvwxyz._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));
GetDatas()[name] = v;
return v;
}
/**
* @brief 查找配置参数
* @param[in] name 配置参数名称
* @return 返回配置参数名为name的配置参数
*/
template<class T>
static typename ConfigVar<T>::ptr Lookup(const std::string& name) {
RWMutexType::ReadLock lock(GetMutex());
auto it = GetDatas().find(name);
if(it == GetDatas().end()) {
return nullptr;
}
return std::dynamic_pointer_cast<ConfigVar<T> >(it->second);
}
/**
* @brief 使用YAML::Node初始化配置模块
*/
static void LoadFromYaml(const std::string& name);
/**
* @brief 加载path文件夹里面的配置文件
*/
static void LoadFromConfDir(const std::string& path, bool force = false);
/**
* @brief 查找配置参数,返回配置参数的基类
* @param[in] name 配置参数名称
*/
static ConfigVarBase::ptr LookupBase(const std::string& name);
/**
* @brief 遍历配置模块里面所有配置项
* @param[in] cb 配置项回调函数
*/
static void Visit(std::function<void(ConfigVarBase::ptr)> cb);
/** 获取加载的配置文件名 */
static std::string& GetFileName() {
static std::string filename;
return filename;
}
private:
/**
* @brief 返回所有的配置项
*/
static ConfigVarMap& GetDatas() {
static ConfigVarMap s_datas;
return s_datas;
}
/**
* @brief 配置项的RWMutex
*/
static RWMutexType& GetMutex() {
static RWMutexType s_mutex;
return s_mutex;
}
};
三、测试
以下为log.yml的配置文件
test.yml
sylar::ConfigVar<int>::ptr g_int_value_config =
sylar::Config::Lookup("system.port", (int)8080, "system port");
sylar::ConfigVar<std::vector<int> >::ptr g_int_vec_value_config =
sylar::Config::Lookup("system.int_vec", std::vector<int>{1,2}, "system int vec");
void test_config() {
std::cout << "before" << std::endl;
std::cout << "system.port: " << g_int_value_config->getValue() << std::endl;
std::cout << "system.int_vec: ";
for(auto i:g_int_vec_value_config->getValue()) {
std::cout << i << " ";
}
std::cout << std::endl;
sylar::Config::LoadFromYaml("/root/code/log_server/bin/conf/test.yml");
std::cout << "after" << std::endl;
std::cout << "system.port: " << g_int_value_config->getValue() << std::endl;
std::cout << "system.int_vec: ";
for(auto i:g_int_vec_value_config->getValue()) {
std::cout << i << " ";
}
std::cout << std::endl;
}
void test_log() {
static sylar::Logger::ptr system_log = SYLAR_LOG_NAME("system");
SYLAR_LOG_INFO(system_log) << "hello system" << std::endl;
std::cout << sylar::LoggerMgr::GetInstance()->toYamlString() << std::endl;
sylar::Config::LoadFromYaml("/root/code/log_server/bin/conf/log.yml");
std::cout << "=============" << std::endl;
std::cout << sylar::LoggerMgr::GetInstance()->toYamlString() << std::endl;
std::cout << "=============" << std::endl;
SYLAR_LOG_INFO(system_log) << "hello system" << std::endl;
system_log->setFormatter("%d - %m%n");
SYLAR_LOG_INFO(system_log) << "hello system" << std::endl;
}
int main(int argc, char** argv) {
test_config();
std::cout << "**********************************************************" << std::endl;
test_log();
return 0;
}