前言

我最早了解到 sigslot 大概是在 2007年 左右,当时在QT中大量使用了 sigslot 的概念。 现在 WebRTC 中也大量使用了 sigslot 这种机制来处理底层的事件。它对我们阅读WebRTC代码至关重要。本篇文章就详细介绍一下 sigslot。

Sigslot作用

Sigslot 的作用一句话表式就是为了解耦。例如,有两个类 A 和 B,如果 B 使用 A, 就必须在 B 类中写入与 A 类有关的代码。看下代码:

class A {
public:void funcA();
}class B {
public:B(A& a){m_a = a;}void funcB(){m_a.funcA(); //这里调用了A类的方法}private:A m_a; //引用 A 类型成员变量。
}void main(int argc, char *argv[]){A a;B b(a);b.funcB();
}
复制代码

这里的弊端是 B 中必须要声名使用 A。如果我们的项目特别复杂,这样的使用方式在后期维护时很容易让我们掉入“陷阱”。有没有一种通用的办法可以做到在 B 中不用使用 A 也可以调用 A 中的方法呢?答案就是使用 sigslot。我们看下面的代码:

class A : public sigslot::has_slot<>
{
public:  void  funcA();
};class B
{
public:  sigslot::signal0<> sender;
};  void main(int argc, char *argv[]){A a;  B b;//在运行时才将 a 和 b 绑定到一起  b.sender.connect(&a, &A::funcA);   b.sender();}
复制代码

通过上面的代码我们可以看到 B 中没有一行与 A 相关的代码。只在 main 函数中(也就是在运行时)才知道 A 与 B 有关联关系。是不是觉得很神奇呢?下面我们就看一下它的实现原理。

实现原理

sigslot的原理其实非常简单,它就是一个变化的观察者模式。观察者模式如下所示:

观察者模式,首先让 Observer(“观察者”)对象 注册到 Subject(“被观察者”) 对象中。当 Subject 状态发生变化时,遍历所有注册到自己的 Observer 对象,并调用它们的 notify方法。

sigslot与观察者模式类似,它使用signal(“信号”)和slot("槽"),区别在于 signal 主动连接自己感兴趣的类及其方法,将它们保存到自己的列表中。当发射信号时,它遍历所有的连接,调用 slot(“槽”) 方法。

如何使用

下面我们看一下 WebRTC 中是如何使用 sigslot 的。

  • 首先,定义 slot("槽"),也就是事件处理函数。在WebRTC中定义槽必须继承 has_slots<>。如下图所示:
  • 其次,定义 signal (“信号”) ,也就是发送的信号。

    sigslot::signal1<AsyncSocket*,sigslot::multi_threaded_local> SignalWriteEvent;
    复制代码
  • 然后,将 signal 与 slot 连接到一起。在这里就是将 AsyncUDPSocket和 OnWriteEvent方法与signal绑定到一起。

    socket_->SignalWriteEvent.connect(this,&AsyncUDPSocket::OnWriteEvent);
    复制代码
  • 最后,发送信号。在 WebRTC中根据参数的不同定义了许多 signal,如 signal1 说明带一个参数,signal2说明带两个参数。

    SignalWriteEvent(this);
    复制代码

关键代码

下面是对 sigslog 的类关系图及关键代码与其详细注释。

...// On our copy of sigslot.h, we set single threading as default.
#define SIGSLOT_DEFAULT_MT_POLICY single_threaded#if defined(SIGSLOT_PURE_ISO) ||                   \(!defined(WEBRTC_WIN) && !defined(__GNUG__) && \!defined(SIGSLOT_USE_POSIX_THREADS))
#define _SIGSLOT_SINGLE_THREADED
#elif defined(WEBRTC_WIN)
#define _SIGSLOT_HAS_WIN32_THREADS
#if !defined(WIN32_LEAN_AND_MEAN)
#define WIN32_LEAN_AND_MEAN
#endif
#include "webrtc/rtc_base/win32.h"
#elif defined(__GNUG__) || defined(SIGSLOT_USE_POSIX_THREADS)
#define _SIGSLOT_HAS_POSIX_THREADS
#include <pthread.h>
#else
#define _SIGSLOT_SINGLE_THREADED
#endif#ifndef SIGSLOT_DEFAULT_MT_POLICY
#ifdef _SIGSLOT_SINGLE_THREADED
#define SIGSLOT_DEFAULT_MT_POLICY single_threaded
#else
#define SIGSLOT_DEFAULT_MT_POLICY multi_threaded_local
#endif
#endif// TODO: change this namespace to rtc?
namespace sigslot {...//这面这大段代码是为了实现智能锁使用的。
//它会根据不同的平台初始化不同的互斥量,并调用不同的锁函数。//如果是 Window 平台
#ifdef _SIGSLOT_HAS_WIN32_THREADS
// The multi threading policies only get compiled in if they are enabled.//如果是全局线程
class multi_threaded_global {public:multi_threaded_global() {static bool isinitialised = false;if (!isinitialised) {InitializeCriticalSection(get_critsec());isinitialised = true;}}void lock() { EnterCriticalSection(get_critsec()); }void unlock() { LeaveCriticalSection(get_critsec()); }private:CRITICAL_SECTION* get_critsec() {static CRITICAL_SECTION g_critsec;return &g_critsec;}
};//如果是本地线程
class multi_threaded_local {public:multi_threaded_local() { InitializeCriticalSection(&m_critsec); }multi_threaded_local(const multi_threaded_local&) {InitializeCriticalSection(&m_critsec);}~multi_threaded_local() { DeleteCriticalSection(&m_critsec); }void lock() { EnterCriticalSection(&m_critsec); }void unlock() { LeaveCriticalSection(&m_critsec); }private:CRITICAL_SECTION m_critsec;
};
#endif  // _SIGSLOT_HAS_WIN32_THREADS//非window平台
#ifdef _SIGSLOT_HAS_POSIX_THREADS
// The multi threading policies only get compiled in if they are enabled.//如果是全局线程
class multi_threaded_global {public:void lock() { pthread_mutex_lock(get_mutex()); }void unlock() { pthread_mutex_unlock(get_mutex()); }private:static pthread_mutex_t* get_mutex();
};//如果是本地线程
class multi_threaded_local {public:multi_threaded_local() { pthread_mutex_init(&m_mutex, nullptr); }multi_threaded_local(const multi_threaded_local&) {pthread_mutex_init(&m_mutex, nullptr);}~multi_threaded_local() { pthread_mutex_destroy(&m_mutex); }void lock() { pthread_mutex_lock(&m_mutex); }void unlock() { pthread_mutex_unlock(&m_mutex); }private:pthread_mutex_t m_mutex;
};
#endif  // _SIGSLOT_HAS_POSIX_THREADS//根据不同的策略调用不同的锁。
//这里的策略就是不同的平台
template <class mt_policy>
class lock_block {public:mt_policy* m_mutex;lock_block(mt_policy* mtx) : m_mutex(mtx) { m_mutex->lock(); }~lock_block() { m_mutex->unlock(); }
};class _signal_base_interface;class has_slots_interface {...public:void signal_connect(_signal_base_interface* sender) {...}void signal_disconnect(_signal_base_interface* sender) {...}void disconnect_all() { ... }
};class _signal_base_interface {...public:void slot_disconnect(has_slots_interface* pslot) {...}void slot_duplicate(const has_slots_interface* poldslot,has_slots_interface* pnewslot) {...}
};// 该类是一个特别重要的类
// signal与slot绑定之前,必须先将槽对象与槽方法组成 connection
//
class _opaque_connection {private:typedef void (*emit_t)(const _opaque_connection*);//联合结构体,用于函数转换template <typename FromT, typename ToT>union union_caster {FromT from;ToT to;};//信号发射函数指针emit_t pemit;//存放“槽”对象has_slots_interface* pdest; // Pointers to member functions may be up to 16 bytes for virtual classes,// so make sure we have enough space to store it.unsigned char pmethod[16];public://构造函数//在构造connect时,要传入槽对象和槽类方法指针template <typename DestT, typename... Args>_opaque_connection(DestT* pd, void (DestT::*pm)(Args...)) : pdest(pd) {//定义成员函数指针,与C语言中的函数指针是类似的typedef void (DestT::*pm_t)(Args...);static_assert(sizeof(pm_t) <= sizeof(pmethod),"Size of slot function pointer too large.");std::memcpy(pmethod, &pm, sizeof(pm_t));//定义了一个函数指针typedef void (*em_t)(const _opaque_connection* self, Args...);//通过下面的方法,将 pemit 函数变理指向了 emitter 函数。union_caster<em_t, emit_t> caster2;//注意 emitter后面的是模版参数,不是函数参数,这里不要弄混了。caster2.from = &_opaque_connection::emitter<DestT, Args...>;pemit = caster2.to;}//返回"槽"对象has_slots_interface* getdest() const { return pdest; }...//因为在构造函数里已经将 pemit 设置为 emitter 了,//所以下面的代码就是调用 emitter 函数。为里只不过做了一次函数指针类型转换。//也就是说调用 connect 的 emit 方法,实际调的是 emitter。template <typename... Args>void emit(Args... args) const {typedef void (*em_t)(const _opaque_connection*, Args...);union_caster<emit_t, em_t> caster;caster.from = pemit;(caster.to)(this, args...);}private:template <typename DestT, typename... Args>static void emitter(const _opaque_connection* self, Args... args) {//pm_t是一个成员函数指针,它指向的是传进来的成员方法typedef void (DestT::*pm_t)(Args...);pm_t pm;std::memcpy(&pm, self->pmethod, sizeof(pm_t));//调用成员方法(static_cast<DestT*>(self->pdest)->*(pm))(args...);}
};//signal_with_thread_policy类的父类。
//该类最主要的作用是存有一个conn list。
//在 signal_with_thread_policy中的connect方法就是对该成员变量的操作。template <class mt_policy>
class _signal_base : public _signal_base_interface, public mt_policy {protected:typedef std::list<_opaque_connection> connections_list;public:...protected://在 _signal_base 中定义了一个connection list,用于绑定的 slots.connections_list m_connected_slots;...};//该类是"槽"的实现
template <class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
class has_slots : public has_slots_interface, public mt_policy {private:typedef std::set<_signal_base_interface*> sender_set;typedef sender_set::const_iterator const_iterator;public:has_slots(): has_slots_interface(&has_slots::do_signal_connect,&has_slots::do_signal_disconnect,&has_slots::do_disconnect_all) {}...private:has_slots& operator=(has_slots const&);//静态函数,用于与signal绑定,由父类调用//它是在构造函数时传给父类的static void do_signal_connect(has_slots_interface* p,_signal_base_interface* sender) {has_slots* const self = static_cast<has_slots*>(p);lock_block<mt_policy> lock(self);self->m_senders.insert(sender);}//静态函数,用于解绑signal,由父类调用//它是在构造函数时传给父类的static void do_signal_disconnect(has_slots_interface* p,_signal_base_interface* sender) {has_slots* const self = static_cast<has_slots*>(p);lock_block<mt_policy> lock(self);self->m_senders.erase(sender);}...private://该集合中存放的是与slog绑定的 signalsender_set m_senders;
};//该类是信号的具体实现
//为了保证信号可以在不同的平台是线程安全的,所以这里使用了策略模式
//mt_policy参数表式的是,不同的平台使用不同的策略
//该类中有两个重要的函数,一个是connect用于与槽进行绑定;另一个是 emit用于发射信号template <class mt_policy, typename... Args>
class signal_with_thread_policy : public _signal_base<mt_policy> {
public:...template <class desttype>void connect(desttype* pclass, void (desttype::*pmemfun)(Args...)) {//这是一个智能锁,当函数结束时,自动释放锁。lock_block<mt_policy> lock(this);//先将对象与"槽"组成一个conn,然后存放到 signal的 conn list里//当发射信号时,调用 conn list中的每个conn的 emit方法。this->m_connected_slots.push_back(_opaque_connection(pclass, pmemfun));//在槽对象中也要保存 signal 对象。pclass->signal_connect(static_cast<_signal_base_interface*>(this));}//遍历所有的连接,并调用 conn 的emit方法。最终调用的是绑定"槽"的方法void emit(Args... args) {lock_block<mt_policy> lock(this);this->m_current_iterator = this->m_connected_slots.begin();while (this->m_current_iterator != this->m_connected_slots.end()) {_opaque_connection const& conn = *this->m_current_iterator;++(this->m_current_iterator);//调 conn 的 emit 方法,最终会调用绑定的 "槽" 方法。conn.emit<Args...>(args...);}}//重载()操作符,这样就从直接调用emit方法变成隐含调用emit方法。void operator()(Args... args) { emit(args...); }
};//下面的对不同参数信号的定义
template <typename... Args>
using signal = signal_with_thread_policy<SIGSLOT_DEFAULT_MT_POLICY, Args...>;template <typename mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
using signal0 = signal_with_thread_policy<mt_policy>;template <typename A1, typename mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
using signal1 = signal_with_thread_policy<mt_policy, A1>;template <typename A1,typename A2,typename mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
using signal2 = signal_with_thread_policy<mt_policy, A1, A2>;...}  // namespace sigslot
复制代码

小结

本文通过 sigslot作用、实现原理、如何使用以及详细的代码注释四个部分剖析了 WebRTC 中的 sigslot。sigslot是 WebRTC中非常底性的基础代码,它对 WebRTC 事件机制起着关键性的作用。熟悉sigslot,对我们阅读 WebRTC 代码会有非常大的帮助。

希望本文能对你有所帮助。谢谢!

深入剖析WebRTC事件机制之Sigslot相关推荐

  1. 数据传递型情景下事件机制与消息机制的架构设计剖析(目录)

    目录 数据传递型情景下事件机制与消息机制的架构设计剖析(一) 转载于:https://www.cnblogs.com/hailan/p/3616766.html

  2. 深入剖析OkHttp系列(五) 来自官方的事件机制

    Event Events allow you to capture metrics on your application's HTTP calls. Use events to monitor: T ...

  3. Qt 事件机制,底层实现原理

    [1]事件 事件是可以被控件识别的操作.如按下确定按钮.选择某个单选按钮或复选框. 每种控件有自己可识别的事件,如窗体的加载.单击.双击等事件,编辑框(文本框)的文本改变事件等等. 事件就是用户对窗口 ...

  4. 「前端面试题系列7」Javascript 中的事件机制(从原生到框架)

    前言 这是前端面试题系列的第 7 篇,你可能错过了前面的篇章,可以在这里找到: 理解函数的柯里化 ES6 中箭头函数的用法 this 的原理以及用法 伪类与伪元素的区别及实战 如何实现一个圣杯布局? ...

  5. jQuery中的事件机制深入浅出

    昨天呢,我们大家一起分享了jQuery中的样式选择器,那么今天我们就来看一下jQuery中的事件机制,其实,jQuery中的事件机制与JavaScript中的事件机制区别是不大的,只是,JavaScr ...

  6. 【初窥javascript奥秘之事件机制】论“点透”与“鬼点击”

    前言 最近好好的研究了一番移动设备的点击响应速度,期间不断的被自己坑,最后搞得焦头烂额,就是现在可能还有一些问题,但是过程中感觉自己成长不少, 最后居然感觉对javascript事件机制有了更好的认识 ...

  7. 【探讨】javascript事件机制底层实现原理

    前言 又到了扯淡时间了,我最近在思考javascript事件机制底层的实现,但是暂时没有勇气去看chrome源码,所以今天我来猜测一把 我们今天来猜一猜,探讨探讨,javascript底层事件机制是如 ...

  8. React事件机制 - 源码概览(下)

    上篇文档 React事件机制 - 源码概览(上)说到了事件执行阶段的构造合成事件部分,本文接着继续往下分析 批处理合成事件 入口是 runEventsInBatch // runEventsInBat ...

  9. 11.QT事件机制源码时序分析(下)

    接上一篇文章https://blog.csdn.net/Master_Cui/article/details/109182406,本文继续解析QCoreApplication::sendEvent和Q ...

最新文章

  1. java线程分类_Java 线程类别
  2. 解决npm 的 shasum check failed for错误
  3. javafx 8u40_JavaFX 8u20天的未来过去(始终在最前面)
  4. JavaFX技巧1:可调整大小的Canvas
  5. php 制作ppt,PPT制作三个基本要素是什么?
  6. G711音频文件播放器
  7. TFTP 简单文件传输协议 简介
  8. java blocked_Java 线程状态之 BLOCKED
  9. 【VUE】vue程序设计----模仿网易严选
  10. android 自定义数字软键盘,(笔记)Android自定义数字键盘
  11. 杰理之使用 mic_rec_play_start()测试 mic 无声的解决方法【篇】
  12. CSS 3.0实现立体书本特效
  13. @Async 异步任务自定义线程池的配置方法和 @Scheduled 定时任务自定义线程池的配置方式
  14. 利用PYTHON出小学数学题
  15. 景区如何设计打造文旅夜游项目
  16. 数据库优化思路 oracle,自己几年前整理的数据库优化技术方案
  17. linux文件系统lv是什么意思,Linux LVM系列(二)lv缩容
  18. mysql表子查询(嵌套查询)
  19. 这些年,联想未能实现的4个战略转型
  20. 事件之事件类型-焦点事件(FocusEvent)

热门文章

  1. linux查看具体进程占用的网络流量
  2. Get value from agent failed: cannot connect to [[192.168.121.128]:10050]:[4]Interrupted systemctl ca
  3. subst 的使用 创建虚拟盘符
  4. string字符串在java_java-String字符串的常用使用方法
  5. 微信小程序获取windowHeight出现不同页面高度不一致问题及解决方案
  6. 01-路由跳转 安装less this.$router.replace(path) 解决vue/cli3.0语法报错问题
  7. JS 框架 :后台系统完整的解决方案
  8. XSS(跨站脚本攻击)漏洞解决方案
  9. 关于跨域获取cookie问题的解决
  10. 在Java 8中,有没有一种简洁的方法可以迭代带有索引的流?