翻译-pjsip开发者指南(一)总体设计

本文档介绍了PJSIP的总体设计,包括其架构、核心组件endpoint的功能,如内存池管理、定时器管理和轮询机制。此外,还讨论了线程安全性和线程复杂性问题,强调了对象与结构的设计原则。
摘要由CSDN通过智能技术生成

英文版的看完就忘,打算把它翻译下来 ,不知道能坚持到第几章,加油吧,一边看一边翻译,难免有错请指正。至于排版实在是很费时间的事情,先这样吧。

 

Chapter 1:General Design

PJSIP是一个用C编写,占用资源少,高性能的sip协议栈。
 1.1 Architecture 
1.1.1 Communication Diagram

 下面的图表展示了sip消息在pjsip组件中的流转。

 1.1.2 Class Diagram
下面的是类图

 1.2 The Endpoint
sip协议栈的核心就是sip endpoint,用不透明的类型pjsip_endpoint来表示。endpoint有以下的属性和功能。
   #pool factory,用来为所有sip组件分配内存池
   #timer heap instance,用来为所有所有sip组件设置timer
   #transport manager instance,用来管理和控制sip传输,以及解析和打印消息
   #PJLIB’s ioqueu,用来分派网络事务的proactor模式(个人注:网络编程中的一种设计模式)
   #polling function,提供线程安全的轮询功能,应用的线程可以轮询timer和socket事务(PJSIP自身不具备创建线程的能力)
   #PJSIP module,enpoint管理pjsip module,module是消息解析和传输之外的一种主要扩展
   #endpoint从transport manager接收sip消息,然后分发到各个module

 1.2.1 Pool Allocations and Deallocations
 sip组件的所有内存分配都通过endpoint,来保证线程安全和约束整个应用的一致性。其中一个策略就是pool cache,未被使用的内存池不会销毁而是留作之后使用。
endpoint使用以下方式来分配和释放内存池
    pjsip_endpt_create_pool()
     pjsip_endpt_release_pool()
endpoint使用 pjsip_endpt_create() 来创建。endpoint一旦创建必须指明应用程序的pool factory。在endpoint整个生命周期内,endpoint都持有该pool factory的指针,并且创建和释放内存池。
 1.2.2 Timer Management
endpoint有一个timer heap来管理timer。所有sip组件的timer创建和设置都是通过endpoint。
endpoint使用以下方式来管理timer
    pjsip_endpt_schedule_timer()
    pjsip_endpt_cancel_timer()
调用endpoint的轮询功能来检查timer的周期。
 1.2.3 Polling the Stack
endpoint通过函数 pjsip_endpt_handle_events()来检查是否有timer和网络事务。应用程序可以指定等待某种事务的时间。
PJSIP协议栈从不创建线程。协议栈中正在运行的都可以认为是application创建的线程,比如API的调用,或者轮询功能的调用。
轮询功能可以根据timer heap的内容来优化等待时间。例如,如果知道一个timer在5ms之后结束,那么就不会花更多的时间等待socket events。当没有网络事务的时候,application就没必要等待那么久。timer的精确度依据不同平台有不同的变化。


 1.3 Thread Safety and Thread Complications
1.3.1 Thread Safety

线程安全是一个复杂的问题。不过幸运是整个协议栈都适用以下设计的规范。
       objects必须是线程安全的,而structure必须不是。
对象和结构在这里来看,区别并没有很清楚。但是给了几个例子帮助理解。
数据结构的例子:
 PJLIB’s data structures, such as lists, arrays, hash tables, strings, and memory pools.
 SIP messaging elements such as URIs, header fields, and SIP messages.
这些结构都不是线程安全的,它们的安全性是由包含它们的对象来确定的。如果数据结构是线程安全的,那么会对协议栈的性能产生影响,并且耗尽操作系统的资源。
相反的, sip的objects必须是线程安全的,以下是例子
    PJLIB objects, such as ioqueue.
     PJSIP objects, such as endpoint, transactions, dialogs, dialog usages, etc.
 1.3.2 The Complications(线程复杂性)
糟糕的是,一些objects在头文件中暴露了它们的定义( e.g. pjsip_transaction and pjsip_dialog)。尽管这些objects暴露的API确定是安全的,但访问应用程序代码中的数据结构的时候,应用程序也必须获得相应的锁。比如objects mutex,调用 pj_mutex_lock() 。
更糟糕的是,dialog公开了用于锁dialog的不同的API。application应调用pjsip_dlg_inc_lock()和pjsip_dlg_dec_lock(),来代替pj_mutex_lock() 和pj_mutex_unlock()。这两者的区别是,dialog的inc/dec锁来确保函数调用的dialog不会被销毁,而dialog的销毁会引起pj_mutex_unlock()的崩溃。
看下面的例子

   1 pj_mutex_lock(dlg->mutex);
   2 pjsip_dlg_end_session(dlg, ...);
   3 pj_mutex_unlock(dlg->mutex);

在以上的例子中,应用程序可能会在第三句崩溃, 因为pjsip_dlg_end_session在一定的条件下,会被销毁。如果发出的invite请求没有响应,那么此次事务将被立即销毁,同时dialog也被销毁。dialog的inc/dec锁通过临时增加dialog会话计数器来防止这个问题,这样dialog就不会在end_session()函数中被销毁。dialog可能在dec_lock()里被销毁。所以一个dialog恰当的锁应该是下面这样的顺序。
    1 pjsip_dlg_inc_lock(dlg);
    2 pjsip_dlg_end_session(dlg, ...);
    3 pjsip_dlg_dec_lock(dlg)

不过,lock的顺序必须是正确的,否则会有死锁发生。比如,应用程序想要在一个dialog中对dialog和transaction都上锁,应用程序必须在transaction的mutex(互斥锁)之前获得dialog的mutex,否则其他的线程此时以相反的顺序获取同一个dialog和transaction,就会发生死锁。
 1.3.3 The Relief
幸运的是,应用程序很少会去直接获取object的mutex,所以上面说的情况极少会出现。
应用程序应该通过object的API来获取可用的object,APIs保证了由于object被删除后的锁的正确性来避免死锁,以及程序崩溃。
当object(dialog或transaction)调用应用程序的回调时,这些回调通常在object的锁已被获取时调用。所以应用程序可以安全的的访问object的数据结构而不用去获取锁。
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值