存档

文章标签 ‘SIP’

关于暂停PJSIP开发指南中文翻译的通知

2010年2月25日

  前一段时间,我曾经翻译过PJSIP Dev’s Guide,中文叫《PJSIP开发指南》,具体可以从这里下载到PDF文件。当时只翻译到第六章,这一段时间也在翻译其它的部分。由于自己近期在研究SIP协议,看到此文档后,决定翻译成中文,方便自己学习,也为学习SIP的朋友提供一些帮助。

  不过,今天发生的一件事,让我决定不再翻译其余的部分。这件事起因于我和PJSIP的作者Benny Prijono的一封邮件,见下图:

  

PJSIP开发指南邮件

PJSIP开发指南

  我给Benny Prijono必了一封邮件询问关于翻译PJSIP开发指南的版权问题,希望作者能同意。很快,Benny回邮件了,非常爽快同意了翻译工作,并且会支持此项目工作。但是作者指出了一个比较严重的问题,这是我没有注意且很沮丧的问题。

  作者指出的问题是:此份PJSIP Dev’s Guide是在PJSIP version 0.5时写成的,现在已经到了1.5.5版本了,同时作者指出,”While most of the key concepts stays the same,some of the details may have changed”,以至于”the concern is that you’re spending a lot of time for something that is not relevant anymore”。也就是说尽管大部分关键概念保持不变,但也有一部分内容改变了,因此很有可能在许多不相关的东西上花费了大量时间。

  在最后,作者说:”So I would say it’ll be more effective if we update the PDF doc first,then translate it to Chinese. Unfortunately we can’t say when we will have time to do this either.”就是说作者希望在先更新了文档后再做翻译工作。但作者又说他他不好说什么时候能有时间做这个工作。

  看了这封邮件后,我觉得没有必须再翻译这份文档了。主要原因是它太旧了,很多概念已经变化了,翻译出来会对使用它的朋友产生误导结果对于已经下载了1-6章的朋友,请仔细研究以确认哪些内容可用,哪些内容已经过时。谢谢!

  不过,现在我仍在研究PJSIP的源代码,以后会不时将自己的心得分享给大家,请大家多多关注。

  

  原创文章如转载,请注明:转载自张文杰的博客 [ http://zhangwenjie.net ]

  本文链接地址:http://zhangwenjie.net/archives/289.html

Windows , ,

PJSIP开发指南中文版(1-6章)

2010年2月8日

     PJSIP是一个SIP协议栈,它支持多种SIP的扩展功能,下面列出其重要的几种优点:
   1.高度的可移殖性 
      只需简单的编译一次,它能够在多种平台上运行(所有Windows 系统列, Windows Mobile, Linux, 所有Unix 系列, MacOS X, RTEMS, Symbian OS, 等等)。 

2.极小的内存需求 
      完全实现SIP的功能只需要150K的内存空间,这使得PJISP不仅仅是嵌入开发的理想平台,并且实用于那些内存运行于极小内存平台的应用,这也意味着极小的用户下载时间。

 3.高效的性能 
      这意味着极小的CPU运算需求下能同时实现更多的通话。

4.支持多种SIP功能及扩展功能 
      多种SIP功能和扩展功能,例如多人会话,事件驱动框架,会话控制(presence),即时信息,电话传输,等等在库文件里得以实现。 

5.丰富文档资料 
      对于软件开发人员来说,文档资料从来都是多多益善,因此我们要求PJSIP开发人员提供了大量的极有价值的文档资料供大家使用。

      PJSIP开发指南详细解释了大多数PJSIP对象的概念、关系(“类图”)以及其他描述;此文档也可以作为PJSIP协议栈的通用设计文档。点这里下载PJSIP开发指南中文版(1-6章)。

   首发张文杰的博客:http://zhangwenjie.net (转载请保留,谢谢。) 本文地址:http://zhangwenjie.net/archives/280.html

Windows ,

PJSIP开发指南翻译系列之第二章

2010年2月3日

  说明:此系列文章是根据PJSIP Dev’ Guide文档翻译而来,所有版权归原作者所有。这里的翻译仅仅是用作学习、交流等用途。本人近期在学习SIP,鉴于网上SIP资料比较少,自己就翻译了此文章,限于自己的SIP及英语水平,错误之处在所难免,希望高手多多指教,也希望有此爱好者一起学习交流。

第二章 模块

模块框架是在PJSIP程序中的各个软件组件中派发SIP消息的主要方法。PJSIP中的所有软件组件,像事务层(transaction layer)和对话层(dialog layer),都以模块的方式来实现。如果没有模块,核心协议栈(pjsip_endpoint和transport)根本不知道如何去处理SIP消息。

模块框架是基于简单但强大的接口抽象。对到来的消息,endpoint(pjsip_endpoint)从优先级高的模块开始向所有的模块派发消息,直到其中一个模块告诉框架它自己处理了此消息。对出去的消息,endpoint在消息真正开始发送到网络之前派发这些消息到所有模块,允许所有模块有机会对消息做最后的修改。

2.1模块特性

2.1.1模块声明

模块接口是在<pjsip/sip_module.h>中声明的:

模块声明

在此声明中,所有的函数指针都是可选的;如果没有指定这些函数指针,那么他们将被认为是成功返回值的。

其中的四个函数指针,load、start、stop、unload是由endpoint调用来控制模块的状态。下面的图显示了模块状态的生命期:

状态图

状态图

on_rx_request()和on_rx_response()函数指针是模块从endpoint(pjsip_endpt)或其它模块接受SIP消息的主要方法。这些回调函数的返回值是很重要的。如果回调函数返回非0值,从语义上意味着这个模块处理了这个消息;这个情况下,endpoint将停止向其它模块派发此消息。在节2.1.3 模块对到来消息的处理中会详细描述这个问题。

on_tx_request()和on_tx_response()函数指针在消息传送之前由传输管理器来调用。这使一些类型的模块(如sigcomp、message signaling)有机会对消息做最后的修改。所有的模块必须返回PJ_SUCCESS(如0状态),否则消息传输将会取消。在节2.1.4 模块对外出消息的处理中会详细描述这个问题。

on_tsx_state()用来在每次事务状态改变时接受通知消息。事务状态改变可能由接受到消息、消息发了出去、定时器消息、传输错误事件等引起。更多关于这个回调函数将在节2.1.5“事务用户和状态回调”中描述。

2.1.2模块优先级

模块优先级指定了哪个模块将被优先调用来处理回调函数的顺序。拥有高优先级的模块(优先级的数字较小)的on_rx_request()和on_rx_response()将先被调用,它的on_tx_request()和on_tx_response()将最后被调用。

下面表出了标准的可设置的模块优先级:

优先级

优先级

记住:较低的优先级数字意味着较高的优先级。

优先级PJSIP_MOD_PRIORITY_TRANSPORT_LAYER是由传输管理器使用的。这个优先级当前只是用于控制消息的传输,比如比这个优先级低的模块(也就是优先级数字较高),它的on_tx_request()/on_tx_response()函数将在消息被传输层(transport layer)处理之被调用;而有高优先级的模块的on_tx_request()/on_tx_response()函数将在消息被传输层(transport layer)处理之被调用。参见2.1.4模块对外出消息的处理以获得更多信息。

PJSIP_MOD_PRIORITY_TSX_LAYER是被传输层(transport layer)模块使用的优先级。传输层(transport layer)将吸收所有属于同一个事务的到来消息。

PJSIP_MOD_PRIORITY_UA_PROXY_LAYER是由UA(如dialog framework)或代理层(proxy layer)使用的优先级。UA层吸收所有属于同一个对话集(dialog set)的到来消息(这也意味着有分歧的回应)。(这里的翻译可能不准确:this means forked responses as well)。

PJSIP_MOD_PRIORITY_DIALOG_USAGE由dialog usages使用。当前PJSIP实现了两种类型的dialog usages:邀请会话(INVITE session)和事件订阅会话(event subscription session)(包括REFER订阅)。Dialog Usage吸收所有在同一个对话中属于一个特定会话的消息。(The dialog usage absorbs messages inside a dialog that belong to particular session)。

PJSIP_MOD_PRIORITY_APPLICATION是典型应用程序模块想使用transactions、dialogs、及dialog usages时可使用的合适优先级值。

  首发张文杰的博客:http://zhangwenjie.net(转载请保留,谢谢)(第二章翻译未结束,待续)

Windows ,

PJSIP开发指南翻译系列之第一章

2010年2月3日

  说明:此系列文章是根据PJSIP Dev’ Guide文档翻译而来,所有版权归原作者所有。这里的翻译仅仅是用作学习、交流等用途。本人近期在学习SIP,鉴于网上SIP资料比较少,自己就翻译了此文章,限于自己的SIP及英语水平,错误之处在所难免,希望高手多多指教,也希望有此爱好者一起学习交流。

  

第一章 通用设计

1.1架构

1.1.1通信图

下面的示意图显示了(SIP)消息是如何在PJSIP组件之间来回传递的。

图表1:协作图

图表1:协作图

1.1.2类图

下面的示意图显示了“类图”:

图表2:类图

图表2:类图

1.2 Endpoint

SIP协议栈的核心就是SIP Endpoint,由不透明的类型pjsip_endpoint来表示。Endpoint具体有以下的属性和职责:

  • 它有内存池工厂,为所有SIP组件分配内存池;
  • 它有定时器堆实例,为所有SIP组件调度定时器;
  • 它有传输管理器实例。传输管理器有SIP传输商品,且控制消息解析和显示;
  • 它拥有单实例的PJSIP ioqueue类型。Ioqueue是用来分派网络事件的proactor 模式;
  • 它提供线程安全的轮询功能,这样应用程序中的线程可以轮询定时器和网络事件(PJSIP本身不创建任何线程);
  • 它管理模块。PJSIP模块是扩展协议栈的主要方法,而协议栈扩展并不局限于消息的解析和显示;
  • 它从传输管理器接受到来的消息,并将这些消息分派到模块。

一些基本功能将会在下面的部分讲述,其余的会在后面的章节讲述。

1.2.1 内存池的分配和释放

SIP组件的所有内存分配都是通过endpoint来完成的,以在整个应用程序中保证线程安全及强制策略的一致性。可应用策略的一个例子是内存池缓存,这里未使用的内存将保留以备将来使用,而不是释放。

Endpoint提供以下方法来分配和释放内存池:

	pjsip_endpt_create_pool()
	pjsip_endpt_release_pool()

当创建endpoint时(使用pjsip_endpt_create()),应用程序必须指定endpoint使用的内存池工厂。Endpont将在自己的生命期内保持此内存池工厂的指针,并由此来分配和释放内存池。

1.2.2定时器管理

Endpoint保留一个单实例的定时器堆来管理定时器。所有定时器的创建和所有SIP组件的定时器调度皆由endpoint来完成。

Endpoint提供以下方法来管理定时器:

	pjsip_endpt_schedule_timer()
	pjsip_endpt_cancel_timer()

当endpont的轮询函数被调用时,endpoint将检查定时器是否过期。

1.2.3轮询协议栈

Endpoint提供了一个单一的函数调用(pjsip_endpt_handle_events())来检查定时器和网络事件的出现。应用程序可以指定它将等待多长时间后去检查这些事件的出现。

PJSIP协议栈从不创建线程。整个协议栈中的代码执行完全代表着应用程序创建的线程,无论是在一个API被调用时,或是应用程序调用轮询方法时。

轮询功能是可以基于定时器堆的内容来优化等待时间的(The polling function is also able to optimize the waiting time based on the timer heap’s contents.)。例如,如果它知道一个定时器将在下一个5秒过期,它等待网络事件的时间将不会超过这5秒;在无网络事件出现时这样做将无必要地延长等待时间。当然定时器的精度在每个平台上都不同。

1.3线程安全和线程复杂性

1.3.1线程安全

线程安全的讨论是一个相当复杂的事情。但是,比较幸运的是,下面的设计原则在整个协议栈中的一致应用:

对象必须是线程安全的;而数据结构必须不是线程安全的。

具体到现在的话题,很自然,对象和简单数据结构的区别不是非常清楚。但是一些例子将会使你对此更明白一点。

数据结构的例子有:

  • PJSIP的数据结构,如链表(lists)、数组(arrays)、哈希表(hash tables)、字符串(strings)、以及内存池。
  • SIP的消息元素,如URLs、header fields、以及SIP消息。

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

相比之下,SIP对象必须是线程安全的。我们称之为对象的例子有:

  • PJLIB对象,如ioqueue
  • PJSIP对象,如endpoint、transactions、dialogs、dialog usages,等等

1.3.2线程复杂性

使事情变糟的是,一些对象在头文件中暴露了它们的声明(例如pjsip_transaction和pjsip_dialog)。尽管这些对象暴露的API是保证线程安全的,应用程序代码在访问这些数据结构之前仍然必须在对象的互斥变量(mutex)上调用pj_mutex_lock()来获取正确的锁。

使事情变得更糟的是,一个dialog提供不同的API来锁定dialog。这样应用程序应该调用pjsip_dlg_inc_lock()和pjsip_dlg_dec_lock(),而不是pj_mutex_lock()和pj_mutex_unlock()。这两种方法的区别是,dialog的inc/dec锁保证dialog不会在函数调用过程中被销毁;不然由于gialog已经被销毁,会使pj_mutex_unlock()崩溃。

考虑下面的例子:

	pj_mutex_lock(dlg->mutex);
	psip_dlg_end_session(dlg,…);
	pj_mutex_unlock(dlg->mutex);

在上面的例子中(假想的),程序可能会在第三行代码处崩溃,因为psip_dlg_end_session()有可能在一定情况下销毁dialog。例如,出去的初始INVITE事务没有得到任何回应,因此事务会马上被销毁,造成dialog也被销毁。Dialog的inc/dec锁通过临时增加dialog会话的计数器来避免这个问题,因而在end_session()中dialog不会被销毁。Dialog可能会在dec_lock()方法中销毁。因此正确锁定dialog的顺序应该像这样:

	pj_mutex_lock(dlg->mutex);
	psip_dlg_end_session(dlg,…);
	pj_mutex_unlock(dlg->mutex);

最后,真正使情事变糟的是,锁定的顺序必须正确,否则可能发现死锁。例如,应用程序在dialog中想既锁定dialog,又锁定transaction,应用程序必须在获取transaction锁之前获取dialog锁,否则当另一个线程正在以相反的顺序获取相同dialog和transaction的锁时,死锁将会发生。

1.3.3解决方法(The Relief)

幸运的是,应用程序很少需要直接获取对象的锁。因此几乎不会出现以上所述的问题。

如果可用,应用程序应该使用对象的API 来存取对象。由于会对对象进行检查,对象的API保证加锁的正确性及避免死锁和崩溃的出现。

当一个对象调用应用程序的回调函数时(如dialog和transaction),这此回调函数在对象的锁获取后正常调用,因此应用程序可以安全访问对象的数据结构而不用获取对象的锁。

  首发张文杰的博客:http://zhangwenjie.net(转载请保留,谢谢)(未完待续)

Windows ,

SIP协议之ReSIProcate环境搭建

2010年1月7日

  这几天在研究SIP协议,并使用开源ReSIProcate搭建了一个简单的测试环境。现在分享给大家,希望对同样研究SIP协议的朋友有一些帮助。

  • 首先下载resiprocate的最新版本:目前是resiprocate-1.6。
  • 取消resiprocate-1.6目录的只读属性,同时 删除resiprocate-1.6目录下的.svn文件夹。此文件夹是隐藏的,需要在Windows系统中设置一下让它显示出来。
  • 然后使用Visual Studio 2008打开resiprocate-1.6下的reSIProcate_9_0.sln解决方案。 
  • 右击reSIProcate_9_0解决方案下的resiprocate项目,选择重新生成,等编译完成后,会在resiprocate-1.6\repro\Debug下生成repro.exe可执行文件。
  •  拷贝resiprocate-1.6\contrib\popt\win32下的libiconv-2.dll、libintl-2.dll、popt1.dll三个dll文件到resiprocate-1.6\repro\Debug。即将libiconv-2.dll、libintl-2.dll、popt1.dll三个dll文件与repro.exe放到同一个目录下,否则repro.exe执行时会出错。
  •  启动一个命令行(控制台)窗口,导航到resiprocate-1.6\repro\Debug目录下,运行repro.exe文件。
  •  打开浏览器,输入地址:http://127.0.0.1:5080。可以看到登录页面:
  • 点击登录链接,输入用户名和密码(均为admin)。出现设置页面。
  •  设置域名:如果是在局域网,域名直接设置为IP地址,如127.0.0.1。点击Add按钮添加域名。
  •  添加用户:这个比较简单,见图所示。注意填写密码。其它选项暂时可以不用去管。如此这样添加两个用户,方便下面对BasicCall项目进行测试。 
  • 右击reSIProcate_9_0解决方案下的basicCall项目,选择重新生成,并设置为启动项目。等编译完成后,右击basicCall项目,选择属性,在弹出的basicCall属性页对话框中,设置basicCall项目的命令行参数。
  •  在basicCall项目中注释掉#define NO_REGISTRATION 1一句。并且,对main函数的开头做如下发动,将日志输出到文件中。
  •  运行basicCall项目,等程序运行结果后,会在resiprocate-1.6\resip\dum\test目录下看到basicCall.txt文件,可以对其进行分析。

  更详细的带图的步骤见这个文章:SIP协议之reSIProcate环境

   首发张文杰的博客:http://zhangwenjie.net(转载请保留)

Windows ,