十四、day14
今天学习如何通过单例模板实现逻辑层
参考:
visual studio配置C++ boost库_哔哩哔哩_bilibili
1. 利用C++11特性封装单例模板
和上一节设计的单例模板有些不同,本节设计的单例模板利用了以下四个C++11新特性,优化了代码
- unique_lock和lock_guard
- once_flag和call_once
- std::function
- condition_variable
1 |
|
- 私有成员仅保留静态成员变量**_instance**,用于保存唯一实例,无需手动进行加锁(C++11新特性会自动实现)
- s_flag是函数GetInstance内的局部静态变量,该变量在函数GetInstance第一次调用时被初始化。以后无论调用多少次GetInstances_flag都不会被重复初始化,而且s_flag存在静态区,会随着进程结束而自动释放。
- call_once只会调用一次,而且是线程安全的, 其内部的原理就是调用该函数时加锁,然后设置s_flag内部的标记,设置为已经初始化,执行lambda表达式逻辑初始化智能指针,然后解锁。第二次调用GetInstance内部还会调用call_once, 只是call_once判断s_flag已经被初始化了就不执行初始化智能指针的操作了。
- PrintAddress()函数获取指向托管对象的原始指针
1)本节GetInstance()函数实现
1 | static std::once_flag s_flag; |
- 使用 std::call_once:这个方法保证了指定的 lambda 函数只会被调用一次。无论有多少线程尝试访问 GetInstance,只有第一个线程会执行初始化代码,其他线程会等待。这种方法内部处理了线程同步,不需要手动加锁。
- 懒汉式加载:确保在第一次调用时实例化,避免了不必要的开销。
2)上一节GetInstance()函数实现
1 | if (single != nullptr) |
- 手动加锁:使用 s_mutex.lock() 和 s_mutex.unlock(),需要手动管理锁。
以上四个C++11新特性的介绍请参考
2. 逻辑类LogicSystem
2.1 逻辑类的声明
首先定义一个逻辑类LogicSystem,并继承单例模板**Singleton<LogicSystem>
**
1 |
|
- FunCallBack为要注册的回调函数类型,其参数为绘画类智能指针,消息id,以及消息内容。
- _msg_que为逻辑队列,其中的元素相当于RecvNode,只不过为了实现伪闭包,所以创建一个LogicNode类,包含Session的智能指针防止被提前释放
- _mutex 为保证逻辑队列安全的互斥量
- _consume表示消费者条件变量,用来控制当逻辑队列为空时保证线程暂时挂起等待,不要干扰其他线程。
- _fun_callbacks表示回调函数的map,根据id查找对应的逻辑处理函数。
- _worker_thread表示工作线程,用来从逻辑队列中取数据并执行回调函数。
- _b_stop表示收到外部的停止信号,逻辑类要中止工作线程并优雅退出。
2.2 LogicNode类
LogicNode类定义在CSession.h中,详细代码可以查看之前的文章
1 | class LogicNode { |
- _session:声明Session的智能指针,实现伪闭包,防止Session被提前释放
- _recvnode:接收消息节点的智能指针
构造函数如下:
1 | LogicNode::LogicNode(std::shared_ptr<CSession> session, std::shared_ptr<RecvNode> recvnode) : |
2.3 逻辑类的实现
a. 逻辑类的构造函数
1 | LogicSystem::LogicSystem() :_b_stop(false) { |
- _b_stop初始化为false,当前工作线程不会停止
- RegisterCallBacks():调用回调注册函数:这个函数用于注册消息处理的回调函数。将不同的消息 ID 和对应的处理函数关联起来,以便在处理消息时能够找到正确的函数。
_worker_thread = std::thread(&LogicSystem::DealMsg, this);
- 创建工作线程:使用 std::thread 创建一个新的线程。
- &LogicSystem::DealMsg:指定要在新线程中执行的成员函数,即 DealMsg,该函数将负责从消息队列中提取消息并处理它。
- this:传递当前对象的指针,以便 DealMsg 可以访问 LogicSystem 的成员变量和函数。
b. RegisterCallBacks(), 注册消息处理的回调函数
1 | void LogicSystem::RegisterCallBacks() { |
将消息ID:MSG_HELLO_WORD映射至回调函数HelloWordCallBack,并存储至_fun_callback。当接收到消息MSG_HELLO_WORD时,系统会调用 HelloWordCallBack 方法来处理消息。
_fun_callback的定义如下:
1 | typedef std::function<void(std::shared_ptr<CSession>, const short& msg_id, const std::string& msg_data)> FunCallBack; |
_fun_callback是一个map,值和键的类型分别为short和FunCallBack,前者是消息id的类型,后者是回调函数。
FunCallBack使用了C++11新特性std::function来声明了一个函数签名,表示接受一个 shared_ptr 指向 CSession、一个 short 类型的消息 ID 和一个 std::string 类型的消息数据的可调用对象
c. HelloWordCallBack, 处理对应消息类型的回调函数
1 | void LogicSystem::HelloWordCallBack(std::shared_ptr<CSession> session, const short& msg_id, const std::string& msg_data) { |
- 使用jsoncpp库包装或者解析数据
- 调用 parse 方法将 msg_data 字符串解析为 JSON 格式,结果存储在 root 中
- 回传消息
d. DealMsg, 处理逻辑队列中的消息流程
1 | void LogicSystem::DealMsg() { |
- DealMsg 逻辑中初始化了一个unique_lock,主要用于控制队列安全,并且配合条件变量可以随时解锁。lock_guard不具备解锁功能,所以此处用unique_lock。
- 若队列为空,并且不是停止状态,就挂起线程。否则继续执行之后的逻辑,如果_b_stop为true,说明处于停服状态,则将队列中未处理的消息全部处理完然后退出循环。如果_b_stop未false,则说明没有停服,是consumer发送的激活信号激活了线程,则继续取队列中的数据处理。
e. 析构函数
1 | LogicSystem::~LogicSystem() { |
逻辑类的析构函数在所有工作线程运行结束后被执行,但工作线程可能会处于挂起状态,此时需要一个激活信号打断**_consume的wait**状态(在该命令前一步将_b_stop置为true)。
f. PostMsgToQue, 添加消息至逻辑队列
1 | void LogicSystem::PostMsgToQue(std::shared_ptr<LogicNode> msg) { |
3. 修改CSession类的headle_read函数
将函数中原本对消息的处理过程(cout读到的消息并回传)删去,改为将消息投至逻辑队列
1 | LogicSystem::GetInstance()->PostMsgToQue(std::make_shared<LogicNode>(shared_from_this(), _recv_msg_node)); |
完整函数代码如下:
1 | void CSession::handle_read(const boost::system::error_code& error, size_t bytes_transferred, |
4. 测试
运行服务器和客户端,测试结果如下