从零开始实现一个C++高性能服务器框架----配置模块

此项目是根据sylar框架实现,是从零开始重写sylar,也是对sylar丰富与完善
项目地址:https://gitee.com/lzhiqiang1999/server-framework

简介

项目介绍:实现了一个基于协程的服务器框架,支持多线程、多协程协同调度;支持以异步处理的方式提高服务器性能;封装了网络相关的模块,包括socket、http、servlet等,支持快速搭建HTTP服务器或WebSokcet服务器。
详细内容:日志模块,使用宏实现流式输出,支持同步日志与异步日志、自定义日志格式、日志级别、多日志分离等功能。线程模块,封装pthread相关方法,封装常用的锁包括(信号量,读写锁,自旋锁等)。IO协程调度模块,基于ucontext_t实现非对称协程模型,以线程池的方式实现多线程,多协程协同调度,同时依赖epoll实现了事件监听机制。定时器模块,使用最小堆管理定时器,配合IO协程调度模块可以完成基于协程的定时任务调度。hook模块,将同步的系统调用封装成异步操作(accept, recv, send等),配合IO协程调度能够极大的提升服务器性能。Http模块,封装了sokcet常用方法,支持http协议解析,客户端实现连接池发送请求,服务器端实现servlet模式处理客户端请求,支持单Reator多线程,多Reator多线程模式的服务器。

配置模块

在这里插入图片描述

1. 主要功能

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

2. 功能演示

  • 配置文件test.yaml
tcp:
    connect:
            timeout: 10000
// 约定的配置
ConfigVar<int>::ptr tcp_config = Config::Lookup("tcp.connect.timeout", 5000, "tcp connect timeout");

LOG_INFO(LOG_ROOT()) << "before: " << tcp_config->getValue();	// before: 5000

// 读取配置文件
YAML::Node root = YAML::LoadFile("test.yaml");
// 加载配置文件,并更改配置值
Config::LoadFromYaml(root);

LOG_INFO(LOG_ROOT()) << "after: " << tcp_config->getValue();	// after: 10000

3. 模块介绍

3.1 ConfigVarBase

  • 配置基本信息。基类。
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, set方法

	virtual std::string toString() = 0;						// 序列化,转换成string
	virtual bool fromString(const std::string& val) = 0;	/
protected:
	std::string m_name;					/// 配置的字段的名字
	std::string m_description;			/// 字段的描述
};

3.2 ConfigVar

  • 配置变量。使用模板的方式完成基本类型,复杂类型(vector、map、set等),自定义类型的序列化和反序列化
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;
		std::string toString() override;
		bool fromString(const std::string& val) override;
		uint64_t addListener(on_change_cb cb);
		
private:
	T m_val;									///	配置变量
	std::map<uint64_t, on_change_cb> m_cbs;		/// 回调函数,当配置有改变时,回调
	RWMutexType m_mutex;						/// 读写锁,读多写少
}
  • 使用模板实现序列化与反序列化
    • ConfigVar 是一个模板类template<class T, class FromStr = LexicalCast<std::string, T>, class ToStr = LexicalCast<T, std::string>>,其中FromStr完成序列化(string to value);ToStr完成反序列化(value to string)。
    • 基本类型。F类型转为T类型
    /**
     * @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);
    	}
    };
    
    • vector的序列化与反序列化。
    /**
     * @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 string [1,2,3] 转换成yaml node"
    		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();
    	}
    };
    
    • map的序列化与反序列化。
    	/**
     * @brief 类型转换模板类片特化(YAML String 转换成 std::map<std::string, T>)
     */
    template<class T>
    class LexicalCast<std::string, std::map<std::string, T> > {
    public:
    	std::map<std::string, T> operator()(const std::string& v) {
    		YAML::Node node = YAML::Load(v);
    		typename std::map<std::string, T> vec;
    		std::stringstream ss;
    		for (auto it = node.begin();
    			it != node.end(); ++it) {
    			ss.str("");
    			ss << it->second;
    			vec.insert(std::make_pair(it->first.Scalar(),
    				LexicalCast<std::string, T>()(ss.str())));
    		}
    		return vec;
    	}
    };
    
    /**
     * @brief 类型转换模板类片特化(std::map<std::string, T> 转换成 YAML String)
     */
    template<class T>
    class LexicalCast<std::map<std::string, T>, std::string> {
    public:
    	std::string operator()(const std::map<std::string, T>& v) {
    		YAML::Node node(YAML::NodeType::Map);
    		for (auto& i : v) {
    			node[i.first] = YAML::Load(LexicalCast<T, std::string>()(i.second));
    		}
    		std::stringstream ss;
    		ss << node;
    		return ss.str();
    	}
    };
    
    • 自定义类型的序列化与反序列化将在后文讲解
  • ConfigVar如何完成序列化与反序列化
    • ConfigVar的模板 template<class T, class FromStr = LexicalCast<std::string, T>, class ToStr = LexicalCast<T, std::string>>。这里使用了模板偏特化,如果T是普通类型(int),会自动匹配到LexicalCast<std::string, int>;如果T是复杂类型(vector<int>),会自动匹配到LexicalCast<std::string, vector<int>>
    • 仿函数实现序列化
    //T to string YAML string
    std::string toString() override
    {
    	try
    	{
    		RWMutexType::ReadLock lock(m_mutex);
    		return  ToStr()(m_val);		// ToStr()(m_val)是仿函数,实际上是因为我们重写了operator()
    	}
    	catch (std::exception& e)
    	{
    		LOG_ERROR(LOG_ROOT()) << "ConfigVar::toString exception"
    			<< e.what() << " convert: " << typeid(m_val).name() << " to string";
    	}
    
    	return "";
    
    }
    
    • 仿函数实现反序列化
    //YAML string to T
    bool fromString(const std::string& val) override
    {
    	try
    	{
    		setValue(FromStr()(val));	// FromStr()(val)是仿函数,实际上是因为我们重写了operator()
    		return true;
    	}
    	catch (std::exception& e)
    	{
    		LOG_ERROR(LOG_ROOT()) << "ConfigVar::fromString exception"
    			<< e.what() << " convert: string to" << typeid(m_val).name();
    	}
    
    	return false;
    }
    
  • 回调机制。加载时完成配置更新。当fromString()时,会调用setValue(),此时会根据新值和旧值,判断是否需要调用回调,完成配置更新
typedef std::function<void (const T& old_value, const T& new_value)> on_change_cb;	// 回调函数的类型
std::map<uint64_t, on_change_cb> m_cbs;		/// 该配置的所有回调

// 添加监听器
uint64_t addListener(on_change_cb cb)
{
	//每次由系统决定key值,再返回,用于delListener
	static uint64_t s_idx = 0;
	RWMutexType::WriteLock lock(m_mutex);	
	++s_idx;
	m_cbs[s_idx] = cb;
	return s_idx;
}

// 设置配置值
void setValue(const T& value) 
{
	{
		RWMutexType::ReadLock lock(m_mutex);
		
		if (value == m_val) return;

		//只有当old 和 new不同时才回调
		for (auto& i : m_cbs)
		{
			i.second(m_val, value);
		}
	}
	RWMutexType::WriteLock lock(m_mutex);
	m_val = value; 
}
  • 这里使用了读写锁保证线程安全,因为配置模块主要是读多写少,能保证最大效率

3.3 Config

  • 配置管理。负责管理所有的配置信息static ConfigVarMap s_datas
//管理类
class Config
{
public:
	typedef RWMutex RWMutexType;
	typedef std::map<std::string, ConfigVarBase::ptr> ConfigVarMap;

	//查找一个配置变量,没找到就创建一个新的,放到s_datas
	//name:A.B
	template<class T>
	static typename ConfigVar<T>::ptr Lookup(const std::string& name, const T& default_value, const std::string& description = "");
	
	//查找name
	template<class T>
	static typename ConfigVar<T>::ptr Lookup(const std::string& name);
	//解析yaml文件,读取配置信息
	static void LoadFromYaml(const YAML::Node& root);
	//查看当前配置表的信息
	static void Visit(std::function<void(johnsonli::ConfigVarBase::ptr)> cb);
	
private:
	//查找name,返回ConfigVarBase::ptr
	static ConfigVarBase::ptr LookupBase(const std::string& name);
	
private:
	/**
	 * @brief 返回所有的配置项
	*/
	static ConfigVarMap& GetDatas() {
		static ConfigVarMap s_datas;
		return s_datas;
	}
	
	//如果是静态成员变量,可能对象会先创建出来,但是静态变量还没有初始化
	static RWMutexType& GetMutex()
	{
		static RWMutexType s_mutex;
		return s_mutex;
	}

};
  • 自定义类型的序列化与反序列化
    • 由于需要完成对日志的配置,因此需要完成对日志器的序列化与反序列化。这里我们需要先抽取出两个结构体 struct LogAppenderDefinestruct LogDefine
    //LogAppender
    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;
    	}
    };
    
    //Logger
    struct LogDefine
    {
    	std::string name;
    	LogLevel::Level level = LogLevel::UNKNOW;
    	std::vector<LogAppenderDefine> appenders;
    	
    	bool operator==(const LogDefine& oth) const
    	{
    		return name == oth.name
    			&& level == oth.level
    			&& appenders == oth.appenders;
    	}
    
    	bool operator<(const LogDefine& oth) const
    	{
    		return name < oth.name;
    	}
    };
    
    • struct LogDefine表示日志器的配置信息,因此需要完成日志器的序列化与反序列化
    //LogDefine偏特化 YAML string to LogDefine
    template<>
    class LexicalCast<std::string, LogDefine>
    {
    public:
    	LogDefine operator()(const std::string& str){...}
    };
    
    //LogDefine偏特化 LogDefine to YAML string 
    template<>
    class LexicalCast<LogDefine, std::string> 
    {
    public:
    	std::string operator()(const LogDefine& i) {...}
    };
    
    • 在加载日志配置时,我们添加了回调函数,实现在加载时就完成配置更改
    ConfigVar<std::set<LogDefine>>::ptr g_log_defines = Config::Lookup("logs", std::set<LogDefine>(), "logs config");
    // LoadFromYaml(YAML string to T) --> 在SetValue中观察者模式触发日志更改事件 --> 调用回调函数(添加、修改、删除) 
    struct  LogIniter
    {
    	LogIniter()
    	{
    		g_log_defines->addListener([] (const std::set<LogDefine>& old_val, const std::set<LogDefine>& new_val) 
    		{
    			// 添加,new_val有,old_val没有
    			// 修改,new_val有,old_val有,但不相同
    			// 删除,new_val没有,old_val有
    		}
    	}
    };
    static LogIniter __log_init;
    
    这里使用了一个方法,使得添加回调函数的执行在main函数之前。在类的构造函数中处理,声明静态变量,这样就会在main函数之前进行构造,完成处理。
    • 日志配置的格式
    logs:
      - name: root
        level: debug
        appenders:
          - type: FileLogAppender
            level: debug
            file: root.log
            formatter: "%d%TF%T[%p]%T[%c]%T%f:%l%T%m%n"
          - type: StdoutLogAppender
            level: debug
            formatter: "%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"
      - name: system
        level: debug
        appenders:
          - type: FileLogAppender
            level: debug
            file: root.log
            formatter: "%d%TF%T[%p]%T[%c]%T%f:%l%T%m%n"
          - type: StdoutLogAppender
            level: debug
            formatter: "%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%F%T[%p]%T[%c]%T%f:%l%T%m%n"
    
  • 下图是整个加载Logger配置的过程
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/6452.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】多态(下)

文章目录1.单继承中的虚函数表整体代码用程序打印虚表如何寻找到虚表地址虚表存在哪里&#xff1f;2.多继承中的虚函数表整体代码寻找虚表地址注意事项多继承重写后的func1的地为什么地址不同&#xff1f;ptr1调用函数——一次jmpptr2 调用函数——多次jmp1.单继承中的虚函数表…

window环境 python ide 安装教程分享

一、 右键-以管理员身份运行 python.exe&#xff08;以安装 3.8 的为例&#xff0c;安 装方法是一样的哈&#xff09; 二、选择你的安装方式。 特别注意&#xff1a;需要把 Add Python ** to PATH 勾选上 ②Customize installation 是自定义安装&#xff0c;安装位置你可以自己…

链表【左程云:Java】

一、单链表 1.单链表的节点结构 2.反转单向和双向链表 2.1 反转单向 package leetcode.链表;/*** author lin* creat 2022--12--12:50** https://leetcode.cn/problems/reverse-linked-list/*/ public class $_206反转链表 {public class ListNode {int val;ListNode next;L…

基于VHDL语言的汽车测速系统设计_kaic

摘 要 汽车是现代交通工具。车速是一项至关重要的指标。既影响着汽车运输的生产率,又关乎着汽车行驶有没有超速违章&#xff0c;还影响着汽车行驶时人们的人身安全。而伴随着我国国民的安全防范意识的逐步增强&#xff0c;人们也开始越来越关心因为汽车的超速而带来的极其严重…

一份sql笔试

1、 select substr(time,1,10),count(order_id),count(distinct passenger_id) from order where substr(time,1,7)2023-08 group by substr(time,1,10) order by substr(time,1,10);2、 select city_id from (select * from order where substr(time,1,7) 2022-08) t1 left j…

【新2023Q2押题JAVA】华为OD机试 - 打折买水果

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本篇题解:打折买水果 题目 有 m m m…

Spring之属性填充

Spring给属性的方式一般有三种 1、通过在属性的添加Autowired注解 Component public class UserService {Autowiredprivate OrderService orderService;public void setOrderService(OrderService orderService) {this.orderService orderService;}public OrderService getO…

b站第一,Python自动化测试实战详细教学,3天教你学会自动化测试

目录 简介 Python自动化测试概述 Python自动化测试目标 Python自动化测试流程 1. 测试计划和设计 2. 测试脚本开发 3. 测试执行和管理 4. 测试维护和优化 Python自动化测试最佳实践 Python自动化测试工具和框架 结论 简介 自动化测试是软件开发过程中一个必不可少的…

【Django 网页Web开发】22. 实战项目:简单的文件上传(15)(保姆级图文)

目录实现效果1. url.py2. upload_list.html3. upload.py总结欢迎关注 『Django 网页Web开发』 系列&#xff0c;持续更新中 欢迎关注 『Django 网页Web开发』 系列&#xff0c;持续更新中 实现效果 1. url.py path(upload/list/, upload.upload_list),2. upload_list.html {% e…

Python中进程和线程到底有什么区别?

人生苦短&#xff0c;我用python python 安装包资料:点击此处跳转文末名片获取 一、进程和线程的关系 线程与进程的区别可以归纳为以下4点&#xff1a; 地址空间和其它资源&#xff08;如打开文件&#xff09;&#xff1a;进程间相互独立&#xff0c;同一进程的各线程间共享。…

操作系统(2.6)--进程通信

进程通信是指进程之间的信息交换。 在进程之间要传送大量数据时&#xff0c;应当利用OS提供的高级通信工具&#xff0c;该工具最主要的特点是: (1)使用方便。OS隐藏了实现进程通信的具体细节&#xff0c;向用户提供了一组用于实现高级通信的命令(原语)&#xff0c;用户可方便地…

ThreeJS-太阳球围绕旋转(二十四)

数学小知识&#xff1a; 我们根据旋转角度θ可以计算出任意时刻的x,y sinθ y0/r; 因此y0 rsinθ, cosθ x0/r,因此x0 rcosθ, 小拓展&#xff1a; y0^ x0^ - r^2*sinθ^2 r^2*cosθ^2 r^2*(sinθ^2 cosθ^2) r^2; 这也是为什么在极坐标方程中 y0 rsinθ, x0 rcos…

15_I.MX6ULL_LCD显示原理

目录 LCD简介 分辨率 像素格式 LCD屏幕接口 LCD时间参数 RGB LCD屏幕时序 像素时钟 显存 LCD简介 LCD全称是Liquid Crystal Display,也就是液晶显示器,是现在最常用到的显示器,手机、电脑、各种人机交互设备等基本都用到了LCD,最常见就是手机和电脑显示器了。LCD的构造…

帮公司面试了一个32岁的程序员,只因这一个细节,被我一眼看穿是培训班出来的,没啥工作经验...

首先&#xff0c;我说一句&#xff1a;培训出来的&#xff0c;优秀学员大有人在&#xff0c;我不希望因为带着培训的标签而无法达到用人单位和候选人的双向匹配&#xff0c;是非常遗憾的事情。 最近&#xff0c;在网上看到这样一个留言&#xff0c;引发了程序员这个圈子不少的…

ChatGPT全球大封号!数10万企业停摆:第一批玩AI的人,被AI给玩了

观点| Mr.K 主笔| Wendy.L 编辑| Emma来源| 技术领导力(ID&#xff1a;jishulingdaoli)3月31日&#xff0c;Open AI就开始无征兆的进行全球大封号&#xff0c;其中亚洲是重灾区&#xff0c;官方没有给出任何声明&#xff0c;具体原因不得而知。并且暂停了这些地区新账号的注…

【从零开始学习 UVM】6.4、UVM 激励产生 —— uvm_do 宏详解

请注意,start方法的call_pre_post字段设置为0,这意味着在使用这些序列宏时,序列的pre_body和post_body方法将永远不会被调用。否则,执行流程与通过start方法执行序列时类似。 文章目录 执行序列宏介绍Example执行序列宏介绍 使用序列宏的优点是可以使用内联约束,但是您失…

实验一 跨VLAN访问

目录 一、按照拓扑图配置VLAN&#xff0c;并实现跨VLAN间的访问。 二、实验环境 三、实验步骤 一、按照拓扑图配置VLAN&#xff0c;并实现跨VLAN间的访问。 1、配置好交换机的VLAN和各个终端的地址&#xff0c;实现各个VLAN内能连通。 2、开启两个交换机的VTY连接&#xff0…

基于STM32F103——XGZP6847D压力传感器+串口打印

基于STM32F103—XGZP6847D压力传感器串口打印基本介绍概述产品特点引脚的连接 (IIC通信)名称含义的介绍I2C通信协议 (设备地址是 0x6D)寄存器描述工作模式寄存器Reg0x30&#xff08;测量命令寄存器&#xff09;Reg0xA5Reg0xA6模式说明组合数据采集模式休眠数据采集模式代码编写…

MyBatisPlus

今日目标基于MyBatisPlus完成标准Dao的增删改查功能掌握MyBatisPlus中的分页及条件查询构建掌握主键ID的生成策略了解MyBatisPlus的代码生成器1&#xff0c;MyBatisPlus入门案例与简介这一节我们来学习下MyBatisPlus的入门案例与简介&#xff0c;这个和其他课程都不太一样&…

[CF复盘] Codeforces Round 863 (Div. 3) 20230404

[TOC]([CF复盘] Codeforces Round 863 (Div. 3) 20230404 ) 一、本周周赛总结 做到E&#xff0c;但DE都TLE&#xff0c;很难受。 A 贪心。 B 坐标运算。 C 贪心构造。 D 分治DFS。 E 九进制模拟。 二、 A. Insert Digit 链接: A. Insert Digit 1. 题目描述 2. 思路分析…