C++实现高性能服务器框架(2) | PHM's world

LOADING

歡迎來到烏托邦的世界

C++实现高性能服务器框架(2)

C++实现高性能服务器(2)—-环境变量模块

配置模块采用约定优于配置的设计思想,让程序所依赖的配置项都有一个默认值,就不需要每次都指定了,这样既简单又灵活。例如通过以下方式设置默认端口号,名称:system.port,值:8080,描述:system port

php复制代码sylar::ConfigVar<int>::ptr g_int_value_config =
    sylar::Config::Lookup("system.port", (int)8080, "system port");

当对YAML文件配置项做出改变时,也会改变相应的配置参数,此时端口号被修改为5050

yaml复制代码system:
    port: 5050      

1. 主要功能

  • 支持yaml格式的配置文件解析
  • 使用模板完成基础类型,复杂类型(vector、map、set等),自定义类型的序列化与反序列化
  • 利用回调机制,在加载配置时,完成配置的更新
  • 使用yaml-cpp库,实现配置文件读取
  • 约定大于配置

配置模块主要有以下几个类:

  • class ConfigVarBase:配置变量的基类
  • class ConfigVar:配置参数模板子类,保存对应类型的参数值,通过仿函数实现stringT类型之间的相互转化
  • class Config: ConfigVar的管理类

最后将日志模块与配置模块整合起来,当配置文件相应参数做出改变时,能够通过回调函数改变相应的参数

class ConfigVarBase(配置变量的基类)

该类为抽象函数,提供三个纯虚函数供子类ConfigVar实现

csharp复制代码virtual std::string toString() = 0; //转化为string
virtual bool fromString(const std::string& val) = 0;    //从string转化为相应类型
virtual std::string getTypeName() const = 0;    //获得该类型的名称

mumber(成员变量)

c复制代码    // 名字
    std::string m_name;
    // 描述
    std::string m_description;

class ConfigVar(配置参数)

c复制代码/* 
 *  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 {};

其中,FromStrToStr使用仿函数片特化的方式,实现不同类型Tstring之间的相互转化,例如vectorstring之间的转化,在转化的过程中,字符串格式都是以YAML为标准。

c复制代码//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);
    }
};

// string To vector
// "[1, 2, 3]" ——> [1, 2, 3]
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;
    }
};

// vector To string
// [1, 2, 3] ——> - 1
//               - 2
//               - 3             
template<class T>
class LexicalCast<std::vector<T>, std::string> {
public:
    std::string operator() (const std::vector<T>& v) {
        YAML::Node node;
        for (auto& i : v) {
            node.push_back(YAML::Load(LexicalCast<T, std::string>()(i)));
        }
        std::stringstream ss;
        ss << node;
        return ss.str();
    }
};

mumber(成员变量)

arduino复制代码    // 参数值
    T m_val;
    // 变更回调函数组, uint64_t key(要求唯一,一般可以用hash)
    // typedef std::function<void(const T &old_value, const T &new_value)> on_change_cb;
    std::map<uint64_t, on_change_cb> m_cbs;
    // 读写锁
    mutable RWMutexType m_mutex;

ConfigVar(构造函数)

配置名描述参数值赋值

c复制代码ConfigVar(const std::string& name
        , const T& defult_val
        , const std::string& description = "")
    : ConfigVarBase(name, description)
    , m_val(defult_val) {

    }

toString(从参数转为string)

若成功,返回转化后的string,失败打出日志,异常以及值的类型

csharp复制代码std::string toString() override {
    try{
        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(m_val).name() << "to String";
    }
    return "";
}

fromString(从string转为值)

YAML String转为参数值,失败打出日志,异常以及值的类型

kotlin复制代码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(m_val).name()
            << " - " << val;
    }
    return false;
}

getValue(获取)和setValue(设置参数)

获取参数直接返回m_val,设置参数时,判断vm_val是否相同,若相同,直接返回,若不相同,则回调变更函数

arduino复制代码// 获取参数
const T getValue() const {
    RWMutexType::ReadLock lock(m_mutex);
    return m_val;
}

// 设置参数
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;
}

addListener(添加变化回调函数)

返回该回调函数对应的唯一id,用于删除回调函数

ini复制代码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;
}

delListener(删除回调函数)

arduino复制代码void delListener(uint64_t key) {
    RWMutexType::WriteLock lock(m_mutex);
    m_cbs.erase(key);
}

getListener(获取回调函数)

ini复制代码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;
}

clearListener(清除回调函数)

csharp复制代码void clearListener() {
    RWMutexType::WriteLock lock(m_mutex);
    m_cbs.clear();
} 

class Config(ConfigVar管理类)

mumber(成员变量)

使用静态方法返回参数,保证初始化顺序

c++保证,函数内的local static对象会在”该函数被调用期间“”首次遇上该对象之定义式“时被初始化。

arduino复制代码// typedef std::unordered_map<std::string, ConfigVarBase::ptr> ConfigVarMap;
// 返回所有的配置项
static ConfigVarMap& GetDatas() {
    static ConfigVarMap s_datas;
    return s_datas;
}

// 配置项的RWMutex
static RWMutexType& GetMutex() {
    static RWMutexType s_mutex;
    return s_mutex;
}

Lookup(获取/创建对应参数名的配置参数)

scss复制代码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()) {
            // 将ConfigVarBase转换为ConfigVar
            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 << " exitst but type not "
                                                  << typeid(T).name() << ", real_type = " << it->second->getTypeName()
                                                  << " " << it->second->toString();
                return nullptr;
            }
        }
    
        // 用于在当前字符串中查找第一个不属于指定字符集合的字符,并返回该字符位置的索引。如果没有找到任何字符,则返回 std::string::npos。
        // name不全在 "abcdefghigklmnopqrstuvwxyz._012345678" 中 
        // name中有非法字符,抛出异常
        if (name.find_first_not_of("abcdefghigklmnopqrstuvwxyz._012345678")
                != std::string::npos) {
            SYLAR_LOG_ERROR(SYLAR_LOG_ROOT()) << "Lookup name invalid" << name;
            throw std::invalid_argument(name);
        }
        
        // 若没有,则创建一个新的ConfigVar
        // typename:用于告诉编译器 ConfigVar<T>::ptr 是一个类型而不是成员变量。
        typename ConfigVar<T>::ptr v(new ConfigVar<T>(name, default_value, description));
        GetDatas()[name] = v;
        return v;
}

Lookup(查找配置参数)

arduino复制代码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);
}

LookupBase(查找配置参数,返回配置参数的基类)

scss复制代码ConfigVarBase::ptr Config::LookupBase(const std::string& name) {
    RWMutexType::ReadLock lock(GetMutex());
    auto it = GetDatas().find(name);
    return it == GetDatas().end() ? nullptr : it->second;
}

LoadFromYaml(使用YAML::Node初始化配置模块)

c复制代码// 递归方式,遍历YAML格式的配置文件中的所有成员,将每个节点的名称和值存在list中
static void ListAllMember(const std::string& prefix,
                          const YAML::Node& node,
                          std::list<std::pair<std::string, const YAML::Node> >& output) {
    // prefix字符不合法
    if (prefix.find_first_not_of("abcdefghigklmnopqrstuvwxyz._012345678") 
            != std::string::npos) {
        SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "Config invalid name: " << prefix << " ! " << node;
        return;
    }
    output.push_back(std::make_pair(prefix, node));
    // 若解析的是map
    if (node.IsMap()) {
        for (auto it = node.begin();
                 it != node.end(); ++it) {
            // 若前缀为空,说明为顶层,prefix为key的值,否则为子层,prefix为父层加上当前层。it->second为当前node
            ListAllMember(prefix.empty() ? it->first.Scalar() : prefix + "." + it->first.Scalar(), it->second, output);
        }
    }
}

void Config::LoadFromYaml(const YAML::Node& root) {
    // 将root中的所有节点信息保存到all_nodes中
    std::list<std::pair<std::string, const YAML::Node> > all_nodes;
    ListAllMember("", root, all_nodes);
    
    // 遍历list
    for(auto &i : all_nodes) {
        std::string &key = i.first;
        if (key.empty()) {
            continue;
        }
        
        // 无视大小写
        std::transform(key.begin(), key.end(), key.begin(), ::tolower);
        // 查找名为key的配置参数
        ConfigVarBase::ptr var = LookupBase(key);
        // 若找到
        if (var) {
            // 若为纯量,则调用fromString(会调用setValue设值)
            if (i.second.IsScalar()) {
                var->fromString(i.second.Scalar());
            // 否则为数组,将其转换为字符串
            } else {
                std::stringstream ss;
                ss << i.second;
                var->fromString(ss.str());
            }
        }
    }
}

Visit(遍历配置模块里面所有配置项)

scss复制代码void Config::Visit(std::function<void(ConfigVarBase::ptr)> cb) {
    RWMutexType::ReadLock lock(GetMutex());
    ConfigVarMap& m = GetDatas();
    for (auto it = m.begin();
         it != m.end(); ++it) {
        cb(it->second);
    }
}

Log和Config整合

log.yml内容格式如下

yaml复制代码logs:
    - name: root
      level: info
      formatter: "%d%T%m%n"
      appenders:
          - type: FileLogAppender
            file: root.txt
          - type: StdoutLogAppender
    - name: system
      level: debug
      formatter: "%d%T%m%n"
      appenders:
          - type: FileLogAppender
            file: system.txt
            formatter: "%d%T[%p]%T%m%n"
          - type: StdoutLogAppender

LogAppender结构体重载==

ini复制代码struct LogAppenderDefine {
    int type = 0;   //1 File, 2 Stdout
    LogLevel::Level level = LogLevel::UNKNOW;
    std::string formatter;
    std::string file;

    bool operator==(const LogAppenderDefine& oth) const {
        return type == oth.type
            && level == oth.level
            && formatter == oth.formatter
            && file == oth.file;
    }
};

Log结构体,重载==<

c复制代码struct LogDefine {
    std::string name;
    LogLevel::Level level = LogLevel::UNKNOW;
    std::string formatter;
    std::vector<LogAppenderDefine> appenders;

    bool operator==(const LogDefine& oth) const{
        return name == oth.name
            && level == oth.level
            && formatter == oth.formatter
            && appenders == oth.appenders;
    }

    bool operator<(const LogDefine& oth) const {
        return name < oth.name;
    }
};

string To LogDefine

c复制代码template<>
class LexicalCast<std::string, LogDefine> {
public:
    // 仿函数
    LogDefine operator()(const std::string& v) {
        // 将文本格式YAML数据v解析为内存中YAML节点n
        YAML::Node n = YAML::Load(v);
        LogDefine ld;
        // 若未定义名为"name"的键,抛出异常
        if(!n["name"].IsDefined()) {
            std::cout << "log config error: name is null, " << n
                      << std::endl;
            throw std::logic_error("log config name is null");
        }
        // 将n["name"]值转为string赋给ld.name
        ld.name = n["name"].as<std::string>();
        // 若n定义了level,则直接赋值,否则为空
        ld.level = LogLevel::FromString(n["level"].IsDefined() ? n["level"].as<std::string>() : "");
        // 若n定义了formatter,则使用当前值,否则为默认的formatter
        if(n["formatter"].IsDefined()) {
            ld.formatter = n["formatter"].as<std::string>();
        }
        
        // 若n定义了appenders
        if(n["appenders"].IsDefined()) {
            // 遍历所有的appenders
            for(size_t x = 0; x < n["appenders"].size(); ++x) {
                auto a = n["appenders"][x];
                // 若当前appender没有定义type,输出错误日志
                if(!a["type"].IsDefined()) {
                    std::cout << "log config error: appender type is null, " << a
                              << std::endl;
                    continue;
                }
                std::string type = a["type"].as<std::string>();
                // 定义LogAppenderDefine对象 lad
                LogAppenderDefine lad;
                // 若type为FileLogAppender
                if(type == "FileLogAppender") {
                    // type置为1
                    lad.type = 1;
                    // 若没有设置文件路径,输出错误日志
                    if(!a["file"].IsDefined()) {
                        std::cout << "log config error: fileappender file is null, " << a
                              << std::endl;
                        continue;
                    }
                    // 设置文件路径
                    lad.file = a["file"].as<std::string>();
                    // 设置appender的formatter
                    if(a["formatter"].IsDefined()) {
                        lad.formatter = a["formatter"].as<std::string>();
                    }
                  // 若type为STdoutLogAppender
                } else if(type == "StdoutLogAppender") {
                    // type置为2
                    lad.type = 2;
                    // 设置appender的formatter
                    if(a["formatter"].IsDefined()) {
                        lad.formatter = a["formatter"].as<std::string>();
                    }
                  // type输入错误
                } else {
                    std::cout << "log config error: appender type is invalid, " << a
                              << std::endl;
                    continue;
                }
                ld.appenders.push_back(lad);
            }
        }
        return ld;
    }
};

LogDefine To string

css复制代码template<>
class LexicalCast<LogDefine, std::string> {
public:
    std::string operator()(const LogDefine& i) {
        YAML::Node n;
        n["name"] = i.name;
        if(i.level != LogLevel::UNKNOW) {
            n["level"] = LogLevel::ToString(i.level);
        }
        if(!i.formatter.empty()) {
            n["formatter"] = i.formatter;
        }

        for(auto& a : i.appenders) {
            YAML::Node na;
            if(a.type == 1) {
                na["type"] = "FileLogAppender";
                na["file"] = a.file;
            } else if(a.type == 2) {
                na["type"] = "StdoutLogAppender";
            }
            if(a.level != LogLevel::UNKNOW) {
                na["level"] = LogLevel::ToString(a.level);
            }

            if(!a.formatter.empty()) {
                na["formatter"] = a.formatter;
            }

            n["appenders"].push_back(na);
        }
        std::stringstream ss;
        ss << n;
        return ss.str();
    }
};

按照YAML配置文件初始化Log

scss复制代码sylar::ConfigVar<std::set<LogDefine> >::ptr g_log_defines =
    sylar::Config::Lookup("logs", std::set<LogDefine>(), "logs config");

struct LogIniter {
    LogIniter() {
        // 添加变化回调函数
        g_log_defines->addListener([](const std::set<LogDefine>& old_value
                    , const std::set<LogDefine>& new_value) {
            SYLAR_LOG_INFO(SYLAR_LOG_ROOT()) << "on_logger_conf_changed";
            for(auto& i : new_value) {
                auto it = old_value.find(i);
                sylar::Logger::ptr logger;
                // new有 old没有 
                if (it == old_value.end()) {
                    //新增logger
                    logger = SYLAR_LOG_NAME(i.name);
                }
                else {
                    if (!(i == *it)) {
                        // 修改的logger
                        logger = SYLAR_LOG_NAME(i.name);
                    } else {
                        continue;
                    }
                }
                // 设置level
                logger->setLevel(i.level);
                // 设置默认formatter
                if (!i.formatter.empty()) {
                    logger->setFormatter(i.formatter);
                }

                logger->clearAppenders();
                // 设置appenders
                for (auto &a : i.appenders) {
                    sylar::LogAppender::ptr ap;
                    // File
                    if (a.type == 1) {
                        ap.reset(new FileLogAppender(a.file));
                        // Stdout
                    } else if (a.type == 2) {
                        ap.reset(new StdoutLogAppender);
                    }
                    // 设置appenderlevel
                    ap->setLevel(a.level);
                    
                    // 设置appender的formatter
                    if(!a.formatter.empty()) {
                        LogFormatter::ptr fmt(new LogFormatter(a.formatter));
                        // 若格式无错误
                        if (!fmt->isError()) {
                            ap->setFormatter(fmt);
                        } else {
                            std::cout << "log.name" << i.name << ", appender type = " << a.type
                                      << ", formatter = " << a.formatter << "is invalid" << std::endl;
                        }
                    }
                    // 放入logger的appender中
                    logger->addAppender(ap);
                }
            }

            for(auto& i : old_value) {
                auto it = new_value.find(i);
                // old有 new没有
                if(it == new_value.end()) {
                    //删除logger
                    auto logger = SYLAR_LOG_NAME(i.name);
                    logger->setLevel((LogLevel::Level)100);
                    logger->clearAppenders();
                }
            }
        });
    }
};

// 保证在main之前初始化
static LogIniter __log_init;