WebRTC 源码导读 102 · sigslot

WebRTC 源码导读 000 · 目录

文中提到的代码引用自 libwebrtc M90 版本 https://github.com/aggresss/libwebrtc/tree/M90

sigslot 源文件位置:

└── src
    └── rtc_base
        └── third_party
            └── sigslot
                ├── sigslot.cc
                └── sigslot.h

libwebrtc 中经常会发现很多类继承 sigslot::has_slots<>,可以看出 libwebrtc 中使用了信号槽语义来表达事件触发机制,先通过一个例子来了解 libwebrtc 中的 sigslot 使用方式:

#include "rtc_base/logging.h"
#include "rtc_base/third_party/sigslot/sigslot.h"
#include "system_wrappers/include/sleep.h"

class Sender {
 public:
  sigslot::signal2<std::string, int> emit_signal;

  void Emit() {
    static int nVal = 0;
    char szVal[20] = {0};
    std::snprintf(szVal, 20, "signal_%d", nVal);
    emit_signal(szVal, nVal++);
  }
};

class Receiver : public sigslot::has_slots<> {
 public:
  void OnSignal(std::string strMsg, int nVal) {
    RTC_LOG(LS_INFO) << "Receive: " << strMsg.c_str() << " ==> " << nVal;
  }
};

int main() {
  rtc::LogMessage::LogToDebug(rtc::LS_VERBOSE);
  Sender sender;
  Receiver recever;

  sender.emit_signal.connect(&recever, &Receiver::OnSignal);

  while (1) {
    sender.Emit();
    webrtc::SleepMs(1000);
  }

  return 0;
}

运行方法参考 https://github.com/aggresss/playground-libwebrtc/blob/playground/playground

通过上面示例可以发现,sigslot 的对外 API 相对比较简单,信号接收者只需要继承 has_slots 类即可拥有 slot 功能,信号发送者也只需要在实例化时确定信号参数,然后通过 connect 和 emit 方法来完成信号发送。但是 sigslots 内部实现时还是需要面临很多挑战,下面通过分析来理解 sigslots 是如何处理这些挑战的。

① 同步机制

在信号实例化时通过策略类模板的方式可以自定义同步方式,同时内置了三种同步机制:

  • single_threaded
  • multi_threaded_global
  • multi_threaded_local

加锁的方式和 C++11 的内部实现机制很像,通过在代码块内(栈空间)实例化 lock_block 时加锁,析构时解锁,通过 C++ 的 RAII (Resource Acquisition Is Initialization) 机制实现锁操作。

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(); }
};

② 连接的双向性

在信号发送前 signal 与 slot 之间需要建立一个连接,这个连接是双向的,也就是说 signal 和 slot 双方共同维护这个连接,连接和断开操作时双方都必须感知,同时任何一方的实例析构时另一方也应感知,同时删除内部维护的连接。所以 _signal_base_interfacehas_slots_interface 都定义了对方发生变更时的操作接口:

class has_slots_interface {
 private:
  typedef void (*signal_connect_t)(has_slots_interface* self,
                                   _signal_base_interface* sender);
  typedef void (*signal_disconnect_t)(has_slots_interface* self,
                                      _signal_base_interface* sender);
  typedef void (*disconnect_all_t)(has_slots_interface* self);

  const signal_connect_t m_signal_connect;
  const signal_disconnect_t m_signal_disconnect;
  const disconnect_all_t m_disconnect_all;

 protected:
  has_slots_interface(signal_connect_t conn,
                      signal_disconnect_t disc,
                      disconnect_all_t disc_all)
      : m_signal_connect(conn),
        m_signal_disconnect(disc),
        m_disconnect_all(disc_all) {}

  // Doesn't really need to be virtual, but is for backwards compatibility
  // (it was virtual in a previous version of sigslot).
  virtual ~has_slots_interface() {}

 public:
  void signal_connect(_signal_base_interface* sender) {
    m_signal_connect(this, sender);
  }

  void signal_disconnect(_signal_base_interface* sender) {
    m_signal_disconnect(this, sender);
  }

  void disconnect_all() { m_disconnect_all(this); }
};
class _signal_base_interface {
 private:
  typedef void (*slot_disconnect_t)(_signal_base_interface* self,
                                    has_slots_interface* pslot);
  typedef void (*slot_duplicate_t)(_signal_base_interface* self,
                                   const has_slots_interface* poldslot,
                                   has_slots_interface* pnewslot);

  const slot_disconnect_t m_slot_disconnect;
  const slot_duplicate_t m_slot_duplicate;

 protected:
  _signal_base_interface(slot_disconnect_t disc, slot_duplicate_t dupl)
      : m_slot_disconnect(disc), m_slot_duplicate(dupl) {}

  ~_signal_base_interface() {}

 public:
  void slot_disconnect(has_slots_interface* pslot) {
    m_slot_disconnect(this, pslot);
  }

  void slot_duplicate(const has_slots_interface* poldslot,
                      has_slots_interface* pnewslot) {
    m_slot_duplicate(this, poldslot, pnewslot);
  }
};

_signal_base_interfacehas_slots_interface 都采用了 NVI (Non-Virtual Interface) 的手法,也就是没有定义虚函数,而是 protect 构造函数,继承时通过初始化列表赋值 private 成员,并提供 public 接口来调用接口函数的方式,这样做的目的主要是为了将与泛型无关的不变代码封装在这两个接口类中,通过分离编译期无关的代码来优化编译时间。

_signal_basehas_slots 在实现时都需要考虑 析构函数、复制构造函数、复制赋值函数的特殊化。

③ 成员函数指针的类型特殊性

C++ 的泛型中实现回调相对于 C 中实现略微复杂一些,主要是 C++ 的类成员函数指针不再是一个单纯的函数调用地址。

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:
  template <typename DestT, typename... Args>
  _opaque_connection(DestT* pd, void (DestT::*pm)(Args...)) : pdest(pd) {
    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...);
    union_caster<em_t, emit_t> caster2;
    caster2.from = &_opaque_connection::emitter<DestT, Args...>;
    pemit = caster2.to;
  }

  has_slots_interface* getdest() const { return pdest; }

  _opaque_connection duplicate(has_slots_interface* newtarget) const {
    _opaque_connection res = *this;
    res.pdest = newtarget;
    return res;
  }

  // Just calls the stored "emitter" function pointer stored at construction
  // time.
  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) {
    typedef void (DestT::*pm_t)(Args...);
    pm_t pm;
    std::memcpy(&pm, self->pmethod, sizeof(pm_t));
    (static_cast<DestT*>(self->pdest)->*(pm))(args...);
  }
};

_opaque_connection 不是一个模版类,两个公共方法是模板化的:构造函数emit,在保存回调方法时需要使用 union_caster 进行一次强制转换才能保存,虽然非模板类的成员变量不支持模板参数,但静态成员函数可以支持模板参数,即编译期实现静态成员函数重载(Overload) ,这就是 union_caster 的设计意图。不将 _opaque_connection 设计成模板类主要是为了避免模板导致的代码膨胀 (code bloat)。

  • typedef void (*emit_t)(const _opaque_connection*) 不是模板函数,所以可以保存为成员变量 pemit ,通过 union_caster 维护它和模板静态成员函数 static void emitter(const _opaque_connection* self, Args... args) 之间的映射。
  • pdestpmethod 为真正的回调类指针和成员函数指针。

参考文档

  1. webrtc的signal slot实现分析 · XXDK141 · CSDN
  2. webrtc 信号槽实现分析 · woder · cnblogs
相关推荐
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页