记录一下最近这学期学习的sylar服务器框架项目,输出整理一下项目的结构,用到的知识和自己的体会
项目仓库地址
https://github.com/sylar-yin/sylar/
整理博客过程中参考的大佬资料链接:
============================================
基础介绍
配置模块的功能是,可以在代码中声明配置项,并从配置文件(.yml)中动态加载配置值
支持的功能有:
-
代码中声明配置项
-
更新配置项
-
从配置文件中加载配置项
-
为配置项注册回调函数
-
导出当前配置
配置模块采用“约定优于配置”的思想,即为所有配置项提供默认值
配置模块大小写不敏感
配置模块主要由下面几个类组成:
- ConfigVarBase 配置项的基类
- LexicalCast 模板类,用于类型转换(内部调用boost::lexical_cast)
- ConfigVar 配置项类
- Config 配置项管理类
该模块使用了yaml-cpp库以及boost库中的boost::lexical_cast函数,编译之前要预先获取,并在cmake中配置路径
ConfigVarBase 配置项的基类
该类为模板类ConfigVar的基类,提供了待实现的虚方法如将字符串转成配置项并设置,将配置项转成字符串等
// 配置项的基类(提供抽象方法等待子类实现)
class ConfigVarBase {
public:
typedef std::shared_ptr
<ConfigVarBase> ptr;
// 构造函数(初始化配置项名称和描述信息)
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); // 大小写不敏感
}
// 虚析构函数
virtual ~ConfigVarBase() {}
// get方法
const std::string& getName() const { return m_name; }
const std::string& getDescription() const { return m_description; }
// 将配置项转成string(抽象方法)
virtual std::string toString() = 0;
// 把string转成配置项的值并设置(抽象方法)
virtual bool fromString(const std::string& var) = 0;
// 获取配置项类型的名称(抽象方法)
virtual std::string getTypeName() const = 0;
// 基类成员设置成protected方便子类访问
protected:
std::string m_name; // 配置项名称
std::string m_description; // 配置项描述信息
};
LexicalCast 模板类
模板类,接收两个模板参数分别是原类型和目标类型,并提供了仿函数接口调用boost的lexical_cast,十分优雅
后续类的方法需要大量使用此接口
// 模板类,用于类型转换(内部调用boost::lexical_cast)
// F:from_type T:to_type
template<class F, class T>
class LexicalCast {
public:
// 仿函数接口(调用形式美观自然)
T operator() (const F& v) {
return boost::lexical_cast
<T>(v);
}
};
由于要配置项的类型可能是复杂类型(如STL容器),例如vector等,而直接调用boost::lexical_cast不能正确解析,故我们要对LexicalCast 模板类进行特定类型的偏特化(模板参数分别是复杂类型和字符串类型),后续如果要支持自定义类型,也需要编写模板偏特化
// LexicalCast模板类的vector偏特化
template<class T> // 将YAML格式的string转为vector的模板类
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];
// 递归调用LexicalCast,支持类型嵌套
vec.push_back(LexicalCast<std::string, T>() (ss.str()));
}
return vec;
}
};
template<class T> // 将vector转为YAML格式的string的模板类
class LexicalCast<std::vector<T>, std::string> {
public:
std::string operator() (const std::vector
<T>& v) {
YAML::Node node;
for(auto& i : v) {
// 递归调用LexicalCast,支持类型嵌套
node.push_back(YAML::Load(LexicalCast<T, std::string>() (i)));
}
std::stringstream ss;
ss << node;
return ss.str();
}
};
// ...
sylar在源码中支持了std::vector,std::list,std::set,std::unordered_set,std::map,std::unordered_map与std::string的转换,足以覆盖大多数情况,并且由于仿函数的偏特化实现采用了递归,使得以上类型的嵌套类型同样支持,太nb了sylar
ConfigVar 配置项类
模板类,模板参数为配置项类型,以及向前写的LexicalCast类对应配置项类型版本的仿函数,该类的成员变量存储了配置项值以及一个回调函数的map(每个回调函数有自己的id),提供了与YAML格式字符串相互转换的方法,以及get和set方法,还有注册/删除回调函数的方法,其中回调函数采用std::function进行包装
// 配置项类(继承自配置项Base类)
// 模板参数: 类型T, 将string转成T的模板类, 将T转成string的模板类
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;
// 回调函数类(使用std::function)的别名
typedef std::function<void (const T& old_value, const T& new_value)> on_change_cb;
// 构造函数(设置名称,描述信息以及默认值)
ConfigVar(const std::string& name, const T& default_value, const std::string& description = "")
: ConfigVarBase(name, description)
, m_val(default_value) {
}
// 返回转成YAML类型的string
std::string toString() override {
try {
RWMutexType::ReadLock lock(m_mutex);
return ToStr() (m_val); // 调用将T类型转成string的模板类的仿函数
} catch(std::exception& e) {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ConfigVar::toString exception"
<< e.what() << " convert: " << typeid(m_val).name() << " to string";
}
return "";
}
// 将YAML类型的string转成T类型,并设置值
bool fromString(const std::string& val) override {
try {
setValue(FromStr() (val)); // 调用从string转成T类型的模板类的仿函数
} catch(std::exception& e) {
SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "ConfigVar::fromString exception"
<< e.what() << " convert: to string" << typeid(m_val).name();
}
return false;
}
// 获取配置项的值
const T getValue() {
RWMutexType::ReadLock lock(m_mutex);
return m_val;
}
// 设置配置项的值,触发回调函数
void setValue(const T& v) {
{
RWMutexType::ReadLock lock(m_mutex);
if(v == m_val) {
return;
}
// 遍历调用m_cbs里的回调函数
for(auto& i : m_cbs) {
i.second(m_val, v);
}
}
RWMutexType::WriteLock lock(m_mutex);
m_val = v; // 修改值
}
// 获取配置项的类型
std::string getTypeName() const override { return typeid(T).name(); }
// 添加回调函数(自动分配key)
uint64_t addListener(on_change_cb cb) {
static uint64_t s_func_id = 0;
RWMutexType::WriteLock lock(m_mutex);
s_func_id++;
m_cbs[s_func_id] = cb;
return s_func_id;
}
// 删除指定key的回调函数
void delListener(uint64_t key) {
RWMutexType::WriteLock lock(m_mutex);
m_cbs.erase(key);
}
// 获取指定key的回调函数
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;
}
// 清空回调函数map
void clearListener() {
RWMutexType::WriteLock lock(m_mutex);
m_cbs.clear();
}
private:
RWMutexType m_mutex;
T m_val; // 配置项的值
std::map<uint64_t, on_change_cb> m_cbs; // 变更回调函数map
};
Config 配置项管理类
使用该类来管理配置项,该类提供了注册/删除配置项的方法
由于在代码中注册配置项大多数情况是在main函数之前的全局里注册,这时候资源尚未初始化,为了解决此问题sylar采用了函数内的static对象会在函数运行时初始化的特性,使用static方法getDatas()来获取资源,以保证资源已初始化
// 配置项管理类
class Config {
public:
typedef std::unordered_map<std::string, ConfigVarBase::ptr> ConfigVarMap; // 配置项map的别名
typedef RWMutex RWMutexType;
// 获取指定配置项(若不存在则创建一个)
template<class T>
static typename ConfigVar
<T>::ptr Lookup(const std::string& name, // 这里加的typename是为了告诉编译器::后面是类型
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("abcdefghijklmnopqrstuvwxyz._0123456789") != 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;
}
// 查找指定名称配置项,返回对应类型指针(若不存在返回空指针)
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); // std::dynamic_pointer_cast<>主要用于shared_ptr<>之间的安全类型转换
}
// 从YAML配置文件中加载配置项
static void LoadFromYaml(const YAML::Node& root);
// 从配置文件夹加载配置项
static void LoadFromConfDir(const std::string& path, bool force = false);
// 查找指定名称配置项,返回基类指针(若不存在返回空指针)
static ConfigVarBase::ptr LookupBase(const std::string& name);
// 便利所有配置项,对所有配置项执行函数cb
static void Visit(std::function<void(ConfigVarBase::ptr)> cb) ;
private:
// 返回存放着所有配置项基类指针的map
static ConfigVarMap& GetDatas() {
static ConfigVarMap s_datas;
return s_datas;
}
// 返回配置项的互斥锁
static RWMutexType& GetMutex() {
static RWMutexType m_mutex;
return m_mutex;
}
};
加载配置文件的方法
支持直接调用LoadFromYaml()方法从一个YAML::Node(需要手动用YAML::LoadFile()创建)中加载一个配置文件
也支持直接传入配置文件夹路径,递归加载文件夹目录下的所有.yml文件(该方法于后来环境变量模块完成后添加)
static sylar::Logger::ptr g_logger = SYLAR_LOG_NAME("system");
ConfigVarBase::ptr Config::LookupBase(const std::string& name) {
RWMutexType::ReadLock lock(GetMutex());
auto it = GetDatas().find(name);
return it == GetDatas().end() ? nullptr : it->second;
}
// 递归解析YAML,结果存放到output
static void ListAllMember(const std::string& prefix,
const YAML::Node& node,
std::list<std::pair<std::string, const YAML::Node>>& output) {
if(prefix.find_first_not_of("abcdefghijklmnopqrstuvwxyz._0123456789")
!= std::string::npos) {
SYLAR_LOG_ERROR(g_logger) << "Config invalid name: " << prefix << " : " << node;
return;
}
output.push_back(std::make_pair(prefix, node));
if(node.IsMap()) {
for(auto it = node.begin(); it != node.end(); ++it) {
ListAllMember(prefix.empty() ? it->first.Scalar()
: prefix + "." + it->first.Scalar(), it->second, output);
}
}
}
// 加载YAML
void Config::LoadFromYaml(const YAML::Node& root) {
std::list<std::pair<std::string, const YAML::Node>> all_nodes;
ListAllMember("", root, all_nodes);
for(auto& i : all_nodes) {
std::string key = i.first;
if(key.empty()) {
continue;
}
std::transform(key.begin(), key.end(), key.begin(), ::tolower);
ConfigVarBase::ptr var = Config::LookupBase(key);
if(var) {
if(i.second.IsScalar()) {
var->fromString(i.second.Scalar());
} else {
std::stringstream ss;
ss << i.second;
var->fromString(ss.str());
}
}
}
}
static std::map<std::string, uint64_t> s_file2modifytime;
static sylar::Mutex s_mutex;
// 从文件夹内加载配置文件
void Config::LoadFromConfDir(const std::string& path, bool force) {
std::string absolute_path = sylar::EnvMgr::GetInstance()->getAbsolutePath(path);
std::vector<std::string> files;
FSUtil::ListAllFile(files, absolute_path, ".yml");
for(auto& i : files) {
{
struct stat st;
lstat(i.c_str(), &st);
sylar::Mutex::Lock lock(s_mutex);
if(!force && s_file2modifytime[i] == (uint64_t)st.st_mtime) {
continue;
}
s_file2modifytime[i] = st.st_mtime;
}
try {
YAML::Node root = YAML::LoadFile(i);
LoadFromYaml(root);
SYLAR_LOG_INFO(g_logger) << "LoadConfFile file="
<< i << " ok";
} catch (...) {
SYLAR_LOG_ERROR(g_logger) << "LoadConfFile file="
<< i << " failed";
}
}
}
测试
注册了一堆不同类型的配置项,先打印一次,然后加载配置文件,再打印一次
#include "sylar/config.h"
#include "sylar/log.h"
#include "sylar/env.h"
#include "sylar/util.h"
#include <yaml-cpp/yaml.h>
#include
<iostream>
sylar::ConfigVar
<int>::ptr g_int_value_config =
sylar::Config::Lookup("system.port", (int)8080, "system port");
// sylar::ConfigVar
<double>::ptr g_int_xvalue_config =
// sylar::Config::Lookup("system.port", (double)8080, "system port");
sylar::ConfigVar
<float>::ptr g_float_value_config =
sylar::Config::Lookup("system.value", (float)11.45f, "system value");
sylar::ConfigVar<std::vector<int>>::ptr g_int_vec_value_config =
sylar::Config::Lookup("system.int_vec", std::vector
<int>{1,2}, "system int vector");
sylar::ConfigVar<std::list<int>>::ptr g_int_list_value_config =
sylar::Config::Lookup("system.int_list", std::list
<int>{1,2}, "system int list");
sylar::ConfigVar<std::set<int>>::ptr g_int_set_value_config =
sylar::Config::Lookup("system.int_set", std::set
<int>{1,2}, "system int set");
sylar::ConfigVar<std::unordered_set<int>>::ptr g_int_uset_value_config =
sylar::Config::Lookup("system.int_uset", std::unordered_set
<int>{1,2}, "system int uset");
sylar::ConfigVar<std::map<std::string, int>>::ptr g_str_int_map_value_config =
sylar::Config::Lookup("system.str_int_map", std::map<std::string, int>{{"k",2}}, "system str_int map");
sylar::ConfigVar<std::unordered_map<std::string, int>>::ptr g_str_int_umap_value_config =
sylar::Config::Lookup("system.str_int_umap", std::unordered_map<std::string, int>{{"k",2}}, "system str_int umap");
void test_config() {
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "Brfore: " << g_int_value_config->getValue();
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "Brfore: " << g_float_value_config->toString();
#define XX(g_var, name, prefix) \
{ \
auto v = g_var->getValue(); \
for(auto& i : v) { \
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << #prefix" "#name": " << i; \
} \
} \
#define XX_M(g_var, name, prefix) \
{ \
auto v = g_var->getValue(); \
for(auto& i : v) { \
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << #prefix" "#name": {" \
<< i.first << " - " << i.second << "}"; \
} \
} \
XX(g_int_vec_value_config, int_vec, before);
XX(g_int_list_value_config, int_list, before);
XX(g_int_set_value_config, int_set, before);
XX(g_int_uset_value_config, int_uset, before);
XX_M(g_str_int_map_value_config, str_int_map, before);
XX_M(g_str_int_umap_value_config, str_int_umap, before);
YAML::Node root = YAML::LoadFile("/home/ayanami/sylar/bin/conf/test.yml");
sylar::Config::LoadFromYaml(root);
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "After: " << g_int_value_config->getValue();
SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "After: " << g_float_value_config->toString();
XX(g_int_vec_value_config, int_vec, after);
XX(g_int_list_value_config, int_list, after);
XX(g_int_set_value_config, int_set, after);
XX(g_int_uset_value_config, int_uset, after);
XX_M(g_str_int_map_value_config, str_int_map, after);
XX_M(g_str_int_umap_value_config, str_int_umap, after);
#undef XX
#undef XX_M
}
int main(int argc, char** argv) {
test_config();
return 0;
}
配置文件内容如下

测试结果如下

可以看到成功从test.yml加载了配置
总结
实现了一个可以从配置文件/文件夹中加载配置的配置模块,这对于大型项目来说必不可缺
后续还能支持定时扫描配置文件如果发现更改重新加载配置等功能,具有高实用性
sylar在配置模块的源码中大量使用了模板相关知识,如偏特化,仿函数等,让我收益良多

ദ്ദി˶˃ ᵕ ˂ )✧