zoukankan      html  css  js  c++  java
  • PJSIP开发指南

    一、通用设计

    1.1   架构

    1.1.1        通信图

    下面的图展示了SIP消息在PJSIP组件间从后端到前端如何传递的。

    1.1.2        类图

    下面的图显示类视图

     

    1.2   Endpoint

    SIP 协议栈的核心是SIP endpoint,它由透明的pjsip_endpoint的表示,endpoint具有下面的属性和职责

    l  内存储工厂,为所有的SIP组件分配内存

    l  具备定时器堆实列,为所有的SIP组件调度定时器

    l  传输管理起实例,传输管理器负责传输SIP消息并且控制消息的解析和打印。

    l  拥有单实例PJLIB io 队列,IO队列采用proactor模式分发事件。

    l  提供了线程安全的轮询功能,应用程序的线程能够查询定时器和socket事件(PJSIP自身不常见任何线程)

    l  管理PJSIP模块,PJSIP模块主要以为着扩展SIP stack,而不仅仅限于消息的解析和打印。

    l  从传输模块接收SIP消息,并且分发到各个模块中。

    1.2.1        内存池分配和释放

    为了保证线程的安全性以及在整个应用中策略强一致性,所有的SIP内存都需要在endpoint中完成分配。比如:内存池缓存,未使用的内存被保留在以后使用而不是销毁。

    Endpoint提供下面的函数分配和释放内存池

    pjsip_endpt_create_pool()

    pjsip_endpt_release_pool()

    当endpoint被pjsip_endpt_create()创建时,应用一定要指明由endpoint使用的内存池工厂。在整个生命周期内Endpoint持有内存池工厂的指针,将来备用创建和释放内存。

    1.2.2        定时器管理

    Endpoint 拥有一个独立定时器堆实例,所有SIP组件的定时器创建和调度都需要通过endpoint 完成。Endpoint提供下面的函数管理定时器

    pjsip_endpt_schedule_timer()

    pjsip_endpt_cancel_timer()

    endpoint的poll函数被调用时,检查定时器是否过期。

    1.2.3         轮询栈

    Endpoint 提供独立的函数(pjsip_endpt_handle_events())为了检查定时器和网络事件的出现,应用可以指定准备等待多久去检查事件的出现。

    Pjsip 栈从不创建线程,整个栈的运行过程都依赖于应用所创建的线程,不管是API被调用或者是轮询函数被调用。

    轮询功能优化等待时间依赖于定时器堆的内容,比如:当它找到定时器将在下个5S过期,他等待socket事件的时间,将不会比5S长,在无网络事件出现时,应用程序等待超过5S是没有必要的,当然每个平台的定时器的精度是不一样的。

    1.3   线程安全和线程的兼容性

    1.3.1        线程安全

    线程安全的讨论是比较麻烦的事情,但是,幸运的是,下面的设计原则,在整个协议中的应用都保持了一致性。

    对象一定是线程安全的,但是数据结构一定不是线程安全的。

    看到这个主题,很自然的想到,对象和简单数据结构的区别不是那么清晰,但是有一些例子可以提供,可能更明白写。

    数据结构的例子:

    PJLIB的数据结构,比如:

    l  链表、数组、哈希表、字符串、内存池。

    l  SIP消息的元素,URLs、header fields和SIP消息

    这些数据结构并不是线程安全的,数据结构的线程安全由包含他们的对象保证。如果数据结构是线程安全的,将会严重的影响协议栈的性能并且消耗操作系统的资源。

    相比之下,PJSIP的对象一定是线程安全的,比如:

    l  PJLIB 对象,比如ioqueue

    l  PJSIP 对象,比如: endpoint、transactions、dialogs等。

     

    1.3.2        复杂性

    更糟糕的是,一些对象在头文件中暴露了他们的声明(pjsip_transaction 和 pjsip_dialog)。

    虽然API暴露可以保证线程的安全性,应用在访问数据结构前,必须通过pj_mutex_lock获取到对象的互斥锁。

    更糟糕的是,dialog暴露的不同的API锁定dialog,应用程序应该调用pjsip_dlg_inc_lock和pjsip_dlg_dec_lock() 替代pj­_mutex_lock和pj_mutex_unlock。两种处理方式区别是:

    Dialog的inc/dec保证了dialog将不会被销毁在函数调用时。而pj_mutex_unlock会因为dialog销毁了而导致层序crash。

    考虑下面的例子:

    pj_mutext_lock(dlg->mutex)

    pjsip_dlg_end_session(dlg,…)

    pj_mutex_unlock(dlg->mutex)

    在上面的例子中,应用可能会crash,因为pjsip_dlg_end_session可能会销毁dialog在某些情况下。例如:INVITE事务没有被应答,事务会被马上销毁,导致dialog被立刻销毁。Dialog

    Inc/dec可以通过增加dialog的引用计数,防止这类情况的发生。再end_session时,dialog 不会被销毁,dialog 可能会在dec_lock函数中被销毁。所以正确的调用顺序应该如下:

    Pjsip_dlg_inc_lock(dlg)

    Pjsip_dlg_end_session(dlg)

    Pjsip_dlg_dec_lock

    最最糟糕的是,锁的调用顺序一定是正确的,否则引起死锁。比如:应用想去锁dialog和dialog 的 transaction,应用必须获得dialog mutex在获取transaction mutex之前,否则当其他的线程以相反的顺序拿锁时,将会引起死锁。

    1.3.3         解决办法

    幸运的是,应用程序很少会直接获取对象的锁,所以上面的问题很少会发生。应用应该使用对象的API访问对象,API会对象检查,保证了lock的正确性,避免了死锁和crash的产生。

    应用程序的回调被对象调用,这些回调被调用时,对象的锁已经获取到了。所以应用能够安全的获取对象的数据结构,不必要获取对象的锁。

  • 相关阅读:
    Swift中的设计模式
    ios应用view之间数据传递的方式
    关于iOS多线程,你看我就够了
    iOS开发-21UINavigationController导航控制器初始化 导航控制器栈的push和pop跳转理解
    iOS蓝牙4.0开发例子
    工作记录8:iOS 传值问题总结(7种传值完美介绍)
    iOS 各种传值方式
    iOS页面间传值的方式(Delegate/NSNotification/Block/NSUserDefault/单例)
    Swift类与结构、存储属性、计算属性、函数与方法、附属脚本等
    源码推荐(7.17):不规则按钮类似于遥控器按钮,一个可以最大程度简化PageView与TabView切换的第三方框架
  • 原文地址:https://www.cnblogs.com/damizhou/p/11706605.html
Copyright © 2011-2022 走看看