深入剖析webrtc事件机制 sigslot

一、什么是信号槽

在构建大型C++项目过程中,如何在各个类之间高效且安全地传递数据或事件是一项具有挑战性的任务。

最直接但并不推荐的方法是使用全局变量。虽然这种方法简单易用,但它会导致命名冲突,难以维护,且全局变量的值容易在不知情的情况下被意外修改。

另一种常见的方式是使用回调函数。在这种情况下,类A会注册类B的回调函数。然而,这种方法的缺点在于对象的声明周期难以控制,经常会出现回调函数触发时,对象已经被销毁的情况。

WebRTC中的信号槽机制是实现通信的一种关键技术。信号槽是一种观察者设计模式,用于在两个对象之间建立一种通信机制。

信号槽机制的基本原理如下:

  1. 信号(Signal):信号是发送方发送的通知,用于告诉接收方某些事情已经发生或某些数据已经可用。在WebRTC中,信号通常表示某种事件,如数据接收、连接状态改变等。

  2. 槽(Slot):槽是接收方注册的回调函数,用于处理接收到的信号。当发送方发送一个信号时,接收方的槽函数将被调用,以便处理信号中的数据或事件。

WebRTC是一种实时通信技术,它允许浏览器之间进行实时音视频通信。在WebRTC中,信号槽是一种用于在两个对象之间传递消息的机制。以下是一个简单的例子,演示如何使用信号槽在WebRTC中传递消息:

#include <iostream>
#include <webrtc/rtcpeerconnection.h>
class receiver: public sigslot::has_slot<>  {
public:
    receiver() {}
    ~receiver() {}
public 
    void onSignalReceived() {
      
    }
};

class Sender {
  public:
    sigslot::signal0<> signal;
}
int main() {
    Sender* sender = new Sender();
    receiver* receiver = new receiver();
    sender->signal.connect(receiver, &receiver::onSignalReceived);
    sender->signal();
    return 0;
}

webrtc中的信号槽是线程安全的。

二、信号槽源码解析

下面根据信号的定义,槽的定义、信号和槽是如何连接的以及信号是如何发送到槽的,逐步探究webrtc的源码实现。

以下的源码解析遵循从顶层到底层,从signal到slot,从定义到发送的逻辑。

2.1 信号的定义

2.1.1 signal

signal 有两种一定,一个是可变参数定义,一种是固定参数的定义,

signal 提供了更大的灵活性和简洁性,允许定义具有任意参数数量的信号,而 signal0 等别名则保留了为特定参数数量信号定义的能力,并提供了对线程策略的直接控制,同时保持了与旧代码的兼容。

// Alias with default thread policy. Needed because both default arguments
// and variadic template arguments must go at the end of the list, so we
// can't have both at once.
template <typename... Args>
using signal = signal_with_thread_policy<SIGSLOT_DEFAULT_MT_POLICY, Args...>;

// The previous verion of sigslot didn't use variadic templates, so you would
// need to write "sigslot::signal2<Arg1, Arg2>", for example.
// Now you can just write "sigslot::signal<Arg1, Arg2>", but these aliases
// exist for backwards compatibility.
template <typename mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
using signal0 = signal_with_thread_policy<mt_policy>;
2.1.2 signal_with_thread_policy
template <class mt_policy, typename... Args>
class signal_with_thread_policy : public _signal_base<mt_policy> {
void connect(desttype* pclass, void (desttype::*pmemfun)(Args...)) {    
        lock_block<mt_policy> lock(this);
        this->m_connected_slots.push_back(_opaque_connection(pclass, pmemfun));
        pclass->signal_connect(static_cast<_signal_base_interface*>(this));//将该信号添加到信号槽中
    }
}

首先根据传入的对象指针pclass和信号触发的成员回调函数pmemfun生成opaque_connectionopaque_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;  // 对象指针
    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; 
    }


    //发射函数
    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...); 
    }
};

2.1.4 _signal_base

主要定义了一个connection list,用于绑定的 slots.

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;
  ...
  
};

2.1.5 _signal_base_interface

对slot_disconnect_t和slot_duplicate的定义,这里采用了NVI设计模式,先看一眼代码,我后面会介绍NVI是什么,以及它的好处是什么

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

NVI(Non-Virtual Interface)的含义

  • 非虚拟接口:公共函数接口是非虚的,它们定义了类的操作方式。比如这里的slot_disconnect 和slot_duplicate

  • 虚函数实现:实际的工作是在私有或保护的虚函数中完成的,这些函数可以在派生类中被重写。比如这里的m_slot_disconnect 和m_slot_duplicate

好处有以下几个

  • 性能优化:虚函数调用通常比非虚函数调用或直接函数指针调用稍慢,因为它需要通过虚表(vtable)进行。虽然这个差异通常很小,但在性能敏感的应用中(如事件处理和信号槽机制),每一点性能的优化都可能很重要。同样,由于不实用虚函数,那每个对象就不用存储指向虚表的指针,这样也可以节省内存。

  • 控制:基类可以在调用虚函数前后执行一些通用的操作,提供更好的控制。比如

 void slot_disconnect(has_slots_interface* pslot) {
     //这里可以添加通用的操作
    m_slot_disconnect(this, pslot);
    //这里可以添加通用的操作
  }
  • 封装性:通过将实现细节隐藏在非公共虚函数中,增强了封装性,从而减少了类接口的复杂性。

  • 扩展性:派生类可以通过重写虚函数来提供特定的行为,而无需修改公共接口。

  • 维护性:NVI 使得维护和更新实现细节变得更加容易,因为这些改变通常不会影响到公共接口。

2.2、槽是如何实现的

2.2.1 has_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) {}

  //析构的时候会断开和信号的连接
  virtual ~has_slots() {
    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);
  }
  
  void disconnect_all()
  {
      lock_block<mt_policy> lock(this);
      const_iterator it = m_senders.begin();
      const_iterator itEnd = m_senders.end();

      while(it != itEnd)
      {
          //使信号断开和slot的连接,防止信号对象向野指针发送消息
          (*it)->slot_disconnect(this);
          ++it;
      }

      m_senders.erase(m_senders.begin(), m_senders.end());
}

  ...
2.2.2 has_slots_interface

这个类的设计和_signal_base_interface差不多,都是使用NVI设计模式,这里就不赘述

class has_slots_interface {
    ...
    typedef void (*signal_connect_t)(has_slots_interface* self,
                                   _signal_base_interface* sender);
    const signal_connect_t m_signal_connect;       
protected:
    has_slots_interface(signal_connect_t conn,
                        signal_disconnect_t disc,
                        disconnect_all_t disc_all)
      : m_signal_connect(conn),                        //设置connect
        m_signal_disconnect(disc),
        m_disconnect_all(disc_all) {}

public:
    void signal_connect(_signal_base_interface* sender) {//调用connect
        m_signal_connect(this, sender);  
    }
    ....
};

2.3 信号是如何发送的

2.3.1 emit

在发送者中执行了信号,这个信号通过重载的()去执行emit函数

 void signal_with_thread_policy:: 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
            _opaque_connection const& conn = *this->m_current_iterator;   
            conn.emit<Args...>(args...);//执行其中保存的回调 
        }
    }
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...); //强转函数指针,调用回调
}
    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...); 
    }

2.4 如何保证线程安全

2.4.1 slot析构了,signal还在

清空sender队列,并且调用signal的slot_disconnect(this);

 //析构函数中断开和信号的连接
~has_slots() { this->disconnect_all(); }  

//最终调用的是如下函数
 static void do_disconnect_all(has_slots_interface* p) {
    has_slots* const self = static_cast<has_slots*>(p);
    lock_block<mt_policy> lock(self);
    while (!self->m_senders.empty()) {
      std::set<_signal_base_interface*> senders;
      senders.swap(self->m_senders);
      const_iterator it = senders.begin();
      const_iterator itEnd = senders.end();

      while (it != itEnd) {
        _signal_base_interface* s = *it;
        ++it;
        //通知信号,slot已经断开了
        s->slot_disconnect(p);
      }
    }
  }
static void do_slot_disconnect(_signal_base_interface* p,
                                 has_slots_interface* pslot) {
    _signal_base* const self = static_cast<_signal_base*>(p);
    lock_block<mt_policy> lock(self);
    connections_list::iterator it = self->m_connected_slots.begin();
    connections_list::iterator itEnd = self->m_connected_slots.end();

    while (it != itEnd) {
      connections_list::iterator itNext = it;
      ++itNext;

      if (it->getdest() == pslot) {
        // If we're currently using this iterator because the signal is firing,
        // advance it to avoid it being invalidated.
        if (self->m_current_iterator == it) {
          self->m_current_iterator = self->m_connected_slots.erase(it);
        } else {
          self->m_connected_slots.erase(it);
        }
      }

      it = itNext;
    }
  }
2.4.2 signal 析构了,slots还在

清空m_connected_slots队列,并且通知slot信号已断开


~_signal_base() { disconnect_all(); }  
 void disconnect_all() {
    lock_block<mt_policy> lock(this);

    while (!m_connected_slots.empty()) {
      has_slots_interface* pdest = m_connected_slots.front().getdest();
      m_connected_slots.pop_front();
      pdest->signal_disconnect(static_cast<_signal_base_interface*>(this));
    }
    // If disconnect_all is called while the signal is firing, advance the
    // current slot iterator to the end to avoid an invalidated iterator from
    // being dereferenced.
    m_current_iterator = m_connected_slots.end();
  }
 
  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);
  }
2.4.3 signal 和 slots同时析构

通过代码分析,可能会产生死锁.
当signal和slots同时释放时,
假设出现这种情况,
signal 锁上了它的s1锁,slots 锁上了它的s2锁,此时signal需要通知slots,然后尝试获取它的s2锁,而slots也需要通知signal,则会尝试获取s1锁,这就会造成相互等待的局面。
尝试在代码中加了sleep,然后开两个线程分别销毁,确实会出问题。
所以尽量保证销毁在一个线程中。

  • 40
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值