在 ZLMediaKit/ZLToolKit 的架构中,模块之间的解耦至关重要。NoticeCenter(通知中心)正是为此而生。它实现了一个观察者模式或发布-订阅模式(Publish-Subscribe Pattern),充当了整个系统的“中枢神经”,负责将事件从发生地精准地投递给感兴趣的监听者。
1. 核心设计理念与原理
NoticeCenter的核心目标是:让事件的产生者(Emitter)不需要知道谁在监听,事件的消费者(Listener)也不需要知道谁发出的事件。
1.1 双层映射结构
为了高效管理成千上万的事件,NoticeCenter采用了双层 Hash Map的结构:
- 第一层(事件层):
NoticeCenter内部维护一个unordered_map,Key 是事件名称(string),Value 是EventDispatcher(事件分发器)。- 作用:根据事件名快速找到对应的分发器。
- 第二层(监听层):每个
EventDispatcher内部维护一个unordered_multimap,Key 是监听者标识(void* tag),Value 是回调函数(Any)。- 作用:存储该特定事件的所有监听回调。
1.2 类型擦除与变参模板
C++ 是强类型语言,但通用的事件中心需要支持任意参数的回调。NoticeCenter结合了以下技术:
Any类型:类似于 C++17 的std::any,用于存储任意类型的函数对象(std::function),实现了回调函数的类型擦除。- 变参模板(Variadic Templates):
emitEvent方法接受ArgsType &&...args,允许发射携带任意数量和类型参数的事件。
2. 核心类分析
2.1EventDispatcher(幕后英雄)
它是实际干活的类,对应“某一个特定事件”的管理者。
- 职责:存储、删除、触发监听器。
- 关键点:
- 防死锁机制:在
emitEvent触发回调时,它会先拷贝一份监听列表,然后释放锁,再遍历拷贝的列表执行回调。 - 为什么这么做?如果在回调函数中又调用了
addListener或delListener,如果不释放锁,就会导致死锁(Self-Deadlock)。 - 中断机制:支持
InterruptException。如果某个监听器抛出此异常,事件广播将立即停止,不再通知后续监听器。
- 防死锁机制:在
2.2NoticeCenter(大管家)
它是单例模式(Singleton)的对外入口。
- 职责:管理所有的
EventDispatcher。 - 接口:
addListener: 注册监听。delListener: 移除监听(支持按 tag 移除单个,或按 tag 移除所有事件的监听)。emitEvent: 发射事件。
2.3NoticeHelper(语法糖)
这是一个模板辅助类,配合宏NOTICE_EMIT使用。
- 作用:简化代码书写,利用模板推导自动匹配参数类型,让调用看起来更像函数调用。
3. 类图概览
4. 关键实现细节解析
4.1 线程安全与性能平衡
NoticeCenter在多线程环境下被高频使用,因此线程安全至关重要。
- 使用了
std::recursive_mutex(递归锁)。这允许同一个线程在持有锁的情况下再次获取锁,防止在复杂的嵌套调用(如回调中移除监听)中发生死锁。 - Copy-On-Write (类似思想):如前所述,
EventDispatcher::emitEvent中:
{std::lock_guard<std::recursive_mutex>lck(_mtxListener);copy=_mapListener;// 1. 临界区内只做拷贝,速度快}// 2. 临界区外执行回调,避免阻塞其他线程的添加/删除操作for(auto&pr:copy){...}4.2tag指针的作用
addListener的第一个参数是void *tag。
- 通常用法:传入
this指针。 - 目的:当对象析构时,需要取消它注册的所有监听。通过
tag(即对象的地址),NoticeCenter可以快速定位并移除该对象注册的所有回调,防止悬垂指针导致的崩溃。
5. 使用场景与示例
场景一:系统启动/停止通知
当服务器初始化完成或准备关闭时,通知各个模块。
监听者 (Listener):
classPlayer{public:Player(){// 关注 "SERVER_INIT" 事件// tag 传 this,方便析构时移除NoticeCenter::Instance().addListener(this,"SERVER_INIT",[](intport,conststring&ip){printf("Server started at %s:%d\n",ip.c_str(),port);});}~Player(){// 移除该对象的所有监听NoticeCenter::Instance().delListener(this);}};触发者 (Emitter):
voidstartServer(){intport=8080;string ip="0.0.0.0";// 广播事件,参数自动透传NoticeCenter::Instance().emitEvent("SERVER_INIT",port,ip);}场景二:使用宏简化调用
ZLToolKit 提供了NOTICE_EMIT宏,让代码更优雅。
// 定义事件参数签名// 这里的 void(int, string) 对应回调函数的签名NOTICE_EMIT(void(int,string),"SERVER_INIT",8080,"0.0.0.0");场景三:中断事件传播
假设有一个鉴权事件,如果第一个监听者鉴权失败,不希望后续监听者继续处理。
NoticeCenter::Instance().addListener(this,"AUTH_USER",[](conststring&user){if(user=="admin"){// 鉴权通过}else{// 抛出中断异常,后续的回调将不会被执行throwEventDispatcher::InterruptException();}});6. 总结
NoticeCenter是 ZLToolKit 中一个短小精悍但功能强大的组件。
- 优点:解耦性强、线程安全、支持任意参数、支持事件中断。
- 注意:
- 回调函数是在触发线程中执行的。如果回调处理耗时过长,会阻塞触发线程。对于耗时操作,建议在回调中抛转到线程池处理。
- 务必在对象析构时调用
delListener(this),否则会导致野指针回调崩溃。
通过理解NoticeCenter,你就能掌握 ZLMediaKit 中流媒体状态变化、Hook 机制等底层通信的脉络。
7. C++11
7.1 using和typedef的区别
using可以直接定义模板函数的别名:
template<typename T> using ptr = std::unique_ptr<T>; ptr<int> p(new int(10)); //正确 template<typename T> typedef std::unique_ptr<T> ptr; //错误 template<typename T> struct A { typedef std::unique_ptr<T> ptr; }; A<int>::ptr x(new int(10)); //正确7.2 function_traits
function_traits是 ZLToolKit 中一个非常核心的 C++模板元编程(Template Metaprogramming)工具类。
它的主要作用是类型萃取(Type Extraction):在编译期解析出任何“可调用对象”(函数、函数指针、std::function、Lambda 表达式、仿函数、成员函数)的详细类型信息,例如返回值类型、参数个数、参数类型等。
template<typename T> struct function_traits; //普通函数, lambda表达式 template<typename Ret, typename... Args> struct function_traits<Ret(Args...)> //特化版本 { public: enum { arity = sizeof...(Args) }; typedef Ret function_type(Args...); typedef Ret return_type; using stl_function_type = std::function<function_type>; typedef Ret(*pointer)(Args...); template<size_t I> struct args { static_assert(I < arity, "index is out of range, index must less than sizeof Args"); using type = typename std::tuple_element<I, std::tuple<Args...> >::type; }; }; //函数指针 template<typename Ret, typename... Args> struct function_traits<Ret(*)(Args...)> : function_traits<Ret(Args...)>{}; //std::function template <typename Ret, typename... Args> struct function_traits<std::function<Ret(Args...)>> : function_traits<Ret(Args...)>{}; //member function #define FUNCTION_TRAITS(...) \ template <typename ReturnType, typename ClassType, typename... Args>\ struct function_traits<ReturnType(ClassType::*)(Args...) __VA_ARGS__> : function_traits<ReturnType(Args...)>{}; \ FUNCTION_TRAITS() FUNCTION_TRAITS(const) FUNCTION_TRAITS(volatile) FUNCTION_TRAITS(const volatile) //函数对象 template<typename Callable> struct function_traits : function_traits<decltype(&Callable::operator())>{};可以通过这个类获取将任何函数类型转为stl的function类型。也可以活获取参数类型。
在noticeCenter里有如下使用:
template<typename FUNC> void addListener(void *tag, FUNC &&func) { using funType = typename function_traits<typename std::remove_reference<FUNC>::type>::stl_function_type; std::shared_ptr<void> pListener(new funType(std::forward<FUNC>(func)), [](void *ptr) { funType *obj = (funType *) ptr; delete obj; }); std::lock_guard<std::recursive_mutex> lck(_mtxListener); _mapListener.emplace(tag, pListener); }这里通过function_traits的stl_function_type得到stl的function类型,并构造了该类型的对象。
7.3 function
可以存储各种函数、函数指针、lambda 表达式等可调用对象。
例如,你可以使用std::function<int(int, float)>来存储一个参数类型为int和float,返回值类型为int的可调用对象。
- 将函数、函数指针、lambda 表达式等存储在一个变量中,方便传递和调用;
- 在不确定可调用对象的具体类型的情况下,接受各种类型的可调用对象;
- 在运行时动态绑定可调用对象;
- 用于实现回调函数等;
- 可以使用
std::bind将函数或函数对象绑定到一个function变量中
例如:
#include <functional> int func(int x, float y) { return static_cast<int>(x + y); } int func2(int x, int y, int z) { return x + y + z; } struct FuncObj { int operator()(int x, float y) { return static_cast<int>(x * y); } }; int main() { std::function<int(int, float)> f1 = func; f1(1, 2.5f); // Output: 3 std::function<int(int, float)> f2 = [](int x, float y) { return static_cast<int>(x * y); }; f2(3, 0.5f); // Output: 1 std::function<int(int, float)> f3 = std::bind(func, std::placeholders::_1, std::placeholders::_2); std::cout << f1(1, 2.5f) << std::endl; // Output: 3 std::function<int(int, float)> f4 = std::bind(FuncObj(), std::placeholders::_1, std::placeholders::_2); std::cout << f4(3, 0.5f) << std::endl; // Output: 1 std::function<int(int)> f5 = std::bind(func2, 1, 2, std::placeholders::_1); std::cout << f5(3) << std::endl; // Output: 6 return 0; }7.4 tuple_element
可以用于提取元组类型中某一位置的元素的类型。
using TupleType = std::tuple<int, float, double>; using ElementType = std::tuple_element<1, TupleType>::type; std::cout << typeid(ElementType).name() << std::endl; // Output: "float"