[C++]sylar高性能服务器框架学习记录:配置模块

记录一下最近这学期学习的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;
}

配置文件内容如下

image-20260206013419391

测试结果如下

image-20260206013505577

可以看到成功从test.yml加载了配置

总结

实现了一个可以从配置文件/文件夹中加载配置的配置模块,这对于大型项目来说必不可缺

后续还能支持定时扫描配置文件如果发现更改重新加载配置等功能,具有高实用性

sylar在配置模块的源码中大量使用了模板相关知识,如偏特化,仿函数等,让我收益良多

评论

  1. Sankkooos
    Android Firefox 134.0
    18 小时前
    2026-2-07 0:40:34

    ദ്ദി˶˃ ᵕ ˂ )✧

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇