PJSIP开发手册之总体设计(一)
来源:互联网 发布:win10如何忘记网络 编辑:程序博客网 时间:2024/05/15 04:17
PJSIP是用C语言写的。
第一章 总体设计
结构
通信图
下面的图展示了SIP消息是如何在SIP组件之间传递的。
类图
下面的图展示的是PJSIP的类
EndPoint
EndPoint是SIP栈的核心,封装在pjsip_endpoint数据结构中。Endpoint有以下属性和职责:
它有内存池工厂,负责给所有的SIP组件分配内存池。
它有定时器堆实例,为所有的SIP组件调度定时器
它有SIP传输管理实例,传输管理有SIP传输和控制消息的解析和打印
它拥有PJLIB io队列的唯一实例。Io队列是用来分发网络事件
它提供线程安全的轮询功能,这样的应用中的线程可以轮询定时器和socket事件(PJSIP本身不创建任何线程)
它管理模块,PJSIP模块是扩展除了消息解析和传输之外协议栈功能的主要方法
它负责从Transport Manager那里接收SIP消息,再分发给各个模块
内存池分配和释放
Endpoint负责所有的SIP组件的内存分配,为了保证整个应用的线程安全和策略的一致性。比如内存池缓存策略,就是把不使用的内存池缓存下来为将来的使用,而不是释放它
Endpoint提供分配和释放内存池的函数:
pjsip_endpt_create_pool()
pjsip_endpt_release_pool()
当endpoint被创建(pjsip_endpt_create())之后,应用为endpoint指定内存池工厂。Endpoint在它整个生命周期内都将保管这个内存池工厂的指针,并且使用它去创建和释放内存池。
定时器管理
Endpoint拥有单一一个定时器堆实例,去管理定时器,完成所有定时器的创建和调度。
Endpoint提供以下函数去管理定时器:
pjsip_endpt_schedule_timer()
pjsip_endpt_cancel_timer()
Endpoint在轮询函数被调用时,会检查定时器的期限。
轮询栈
Endpoint提供一个函数(pjsip_endpt_handle_events())去检查定时器超时和网络事件的发生。应用可以指定等待这些事件发生的最长时间。
PJSIP栈不会创建线程。所有栈的执行都会在应用的主线程中,不管是使用API调用还是应用调用轮询函数。
轮询功能是基于定时器堆的内容来有优化等待时间的。例如,它知道一个定时器将在下一个5ms超时,它等待socket事件的时间就不会超过5ms;这样就减少了当没有socket事件发生时,应用所需要的等待时间。定时器的精确度依平台而定。
线程安全与线程并发
线程安全
线程安全是个复杂的问题。但是,幸运的是,以下的设计原则在整个栈中都一致使用了:
对象一定要是线程安全的,但是数据结构不一定是线程安全的。
通常,对象和简单数据之间没有明显的区别。但是下面的例子可以让你了解它们之间的区别:
数据结构是:
- PJLIB的数据结构,像列表、数组、哈希表、字符串和内存池
- SIP消息元素,像URIs,头部域和SIP消息
数据结构都是线程不安全的,它们的线程安全是由包含它们的对象保证的。如果把数据结构也设置为线程安全的。我们称之为对象的有:
PJLIB对象,像io队列的,那么将会严重影响栈的性能和浪费操作系统的资源
- 然而,SIP对象必须是线程安全
- PJSIP对象,像endpoint,transaction,dialogs和dialogusages等等
线程并发
但是糟糕的是一些对象在头文件里暴露了它们的声明(如pjsip_transaction和pjsip_dialog)。虽然这些对象暴露的API是保证线程安全的。但是应用还是必须保证在访问这些数据结构之前获取到相应的锁,例如调用pj_mutex_lock()
更糟糕的是,一个dialog暴露了除了pj_mutex_lock()和pj_mutex_unlock()之外的不同的APi来锁一个dialog。应用应该调用pjsip_dlg_inc_lock()和pjsip_dlg_dec_lock()。这两种方法之前的区别,是dialog inc/dec锁方法在调用过程中并不会销毁dialog,不然dialog销毁会导致pj_mutex_unlock()崩溃。
考虑下面的例子:
上面的例子(假象的),应用执行到第3行可能会崩溃,由于pjsip_dlg_end_session()在某种情况下,可能会销毁dialog,例如,当发出的初始的INVITE事务没有收到任何响应,因此事务将被销毁,同时dialog也将被销毁。Inc/dec方法通过临时增加会话计数来阻止dialog的销毁,因此在end_session()方法中将不会销毁dialog。Dialog可能在dec_lock()方法中被销毁。正确的顺序应该如下:
最后,真正糟糕的是,加锁必需在正确的顺序,否则将会造成死锁。例如,如果应用想要锁定同一个dialog中的dialog和一个事务,必须在获得transaction的锁之前先获得dialog的锁,否则当另一个线程以相反的顺序获取同一个dialog和transaction的锁时会导致死锁的发生。
解决方法
幸运的是,应用只有在极少的情况下需要直接获取对象的锁,所以上面的问题很少会发生。
应用应该使用对象的API去访问对象。APIs保证正确的执行顺序,避免死锁和崩溃。
回调函数通常都是在已经获取到对象的锁之后,才会被对象(transaction或dialog)调用。应用不用获取对象的锁,也可以安全地访问对象的数据结构。
- PJSIP开发手册之总体设计(一)
- PJSIP开发手册之模块(二)
- PJSIP开发手册之事务(八)
- PJSIP开发手册之即时消息(十六)
- PJSIP开发手册之消息发送(七)
- PJSIP开发手册之认证框架(九)
- PJSIP开发手册之用户代理(十)
- PJSIP开发手册之SDP offer/answer框架(十一)
- PJSIP开发手册之SIP事件通知(十三)
- PJSIP开发手册之Presence事件包(十四)
- PJSIP开发手册之Refer事件包(十五)
- PJSIP开发手册之消息元素(三)
- PJSIP开发手册之解析器(四)
- PJSIP开发手册之消息缓存区(五)
- PJSIP开发手册之传输层(六)
- PJSIP开发手册之Dialog Invite会话和Usage(十二)
- iOS开发之PJSIP
- PJSIP手册
- 学习地址
- Linux ALSA声卡驱动之二:声卡的创建
- Collection 和 Collections的区别
- Unity3D研究院之Assetbundle的实战(六十三)
- Lua 项目分析、创建新场景
- PJSIP开发手册之总体设计(一)
- CAGradientLayer(简用)
- Android ProGuard实例教程
- iphone开发中的一些小技巧
- Linux ALSA声卡驱动之三:PCM设备的创建
- Android 开发------------------ 修改 Actionbar 的样式
- JAVA5新特性
- viewpage 实现左右循环滑动
- UIBezierPath+CAShapeLayer 绘制自定义图形