在Vovida的基础上实现自己的SIP协议栈(一)


卢政 2003/08/01

写在前面的话

  不少通讯方面的同好已经读了我在去年岁末撰写的《如何用OpenH323开发自己的H.323协议栈》,大都给予了很高的评价,甚至可以说是好评如潮,说来惭愧,我只不过把十几个人的工作进行了整理和归纳而已,事实上我自己的代码只有很少的一部分(主要在H.245/H.235部分),后来很多朋友向我索要RTH323的测试版本一直未果,我在这里说明一下,由于该软件的使用和二次开发的权利已经被某欧洲公司所买断,所以我已经无权发布测试代码,如有不便敬请大家原谅.

  在我们开发RTH323之际,我已经开始注意SIP协议了,并且根据RFC2543设计了不少实验代码,因为当时的开发有一个H.323-SIP的翻译网关的需求,不过后来这个计划又取消了,也从这个时候开始我逐渐对SIP有了比较浓厚的兴趣,只是并没有做什么实际的工作,仅仅初步了解了一下协议的整体构造.

  直到去年年底,我接触了Vovida的开放原码的SIP系统--Vocal以后,我决定开始系统的了解SIP的整个构造,我个人认为Vocal是一个非常典型的SIP系统,里面包含了所有构造电信级呼叫中心,区域网关以及中继网关的所有内容,而且代码清晰,比较易于改造;于是我花费了大概6个月的时间阅读了整个Vocal系统的代码,并在很多重要的地方做了标注.正好在今年的5月份,我的公司有构造一个大型的Voice/Video IP企业呼叫中心的计划,我就把我对Vocal的研究成果提交给公司,方案得到通过,现在我的公司正在和国内某个知名大学合作准备在现有Vocal基础上改造一个企业级的视频电话呼叫平台

  不过我个人而言,对这个计划不是非常的满意,由于时间和金钱上的限制,大部分的电讯级补充服务第一阶段没有实现的,只可能在第二阶段去实现该计划,时间可能要持续很长,所以我也很希望有其他的开发公司参与完成这个方面的开发,当然我们也可以为有兴趣的公司提供技术咨询或者展开合作。

  我写这本文的目的在于公布我个人对Vocal这个开放原代码系统的一些研究成果,当然里面有很多地方没有说得非常清楚,本身要用文字来阐述程序的设计思想就是一个非常困难的事情,所以我在里面大量的绘制了很多图表,来帮助读者阅读本文,这次公布的是有关UA端的内容,后续在本文中将介绍Vocal系统中的Provision Server; Marshal Server; Redirect Server; HeartBeat Server:Policy Server:CDR Server:Network Manager:Feature Server:以及各种协议的Translator,读者需要具备对SIP,H.323,MGCP,QoS的基本了解,以及对Java,XML, Call Processing Language,C++的知识,在以下章节中不会对这些基本的知识做太详细的介绍。

  预计本文全部刊登完毕大概需近一年的时间,我希望有软件开发公司来和我合作完成这篇文章或者是共同从事Vocal系统的二次开发工作。

  从这半年的阅读Vocal的过程中我体会到Vocal系统的商业应用价值非常大,有人在论坛上和我讨论:改造别人的应用平台的难度和重新开发一个应用的难度哪个更大,双方都各置一词,就我个人觉得,大型的商业软件如果按照开放原代码来进行改造,特别是一些基础平台(例如操作系统),速度肯定是比从零开始要快很多的,RTH323就是一个成功的范例,这也是国内很多软件厂商的基本运行模式,特别对于一些人员,技术,资金都不是非常充裕的公司这可能也是唯一的方案,但是开放原代码的软件大部分的效率非常低下,而且代码冗余,注释比较少,可读性非常差,甚至还有一些致命错误(Vocal中的Feature Server中就有这样的错误,往往可以造成系统崩溃)所以这样做的前提条件就是能把握住工作的重点,能及时发现并排除问题,这样才可能把开放原代码改造成高效率的商业应用。

目 录

一.楔子
二.H.323和SIP之间的差异
三.本文的主要内容
1.User Agent的简介
2.UA部分主要程序部分的介绍
2.1 主程序:\SIP\UA\UA.cxx
2.2 创建一个User Agent的实体
2. 3 HeartLessProxy的创建
2.4 让User Agent Run起来
2. 5 HeartLessProxy Run方法的实现
2. 5. 1 WorkerThread的Run方法
2.5.1.1 processSipEvent
2. 5. 1. 2 processUaDeviceEvent
2.5.1.3 processUaDigitEvent
2. 5. 2 SipThread的Run方法
2. 6 在User Agent中的四个重要实例的Run方法
2. 6. 1 媒体设备启动
2.6.2 启动RTP线程,用于对RTP/RTCP包的接收和发送管理;
2. 6. 3 合法用户列表的获取(Redirection Server专用)
2. 6. 4 监测线程:
2. 6. 5 自动呼叫
3.开始一个呼叫和等待对方呼叫:
3. 1 系统创建StateIdle状态:
3. 2 开始一个呼叫:
3. 2. 1 OpStartCall主程序部分:
3. 2. 2 取得键盘的事件
3. 2. 3 状态机(State)对各个操作(Operator)的处理过程:
3. 2. 4 开始一个呼叫所经历的各种操作(Operator)
3. 2. 5 如何进入待机状态(Idle状态)
3. 2. 6 如何开始拨号并且开始一个呼叫:
3. 2. 6. 1 OpStartDialTone本地发送拨号音;
3. 2. 6. 2 OpAddDigit输入电话号码开始拨号:
3. 2. 6. 3 OpStopDialTone;
3. 2. 6. 4 OpInviteUrl建立一个INVITE消息并且发送到被叫;
3.2.7 进入Trying状态
3. 2. 7. 1 OpStartTimer启动每个事件的定时器:
3. 2. 7. 2 挂机事件的检测机制
3. 2. 7. 3 OpStartRingbackTone向被叫进行铃声回放。
3.2.7.4 OpReDirect进行重定向服务的操作
3.2.7.5 授权检查
3.2.7.6 OpFarEndAnswered处理接收到的OK回应
3.2.7.7 在Vocal中如何实现RSVP资源预留协议
3.2.8用户处于通话的StateInCall状态:
3. 2. 8. 1 OpStartAudioDuplex主叫打开RTP通道
3. 2. 8. 2 处理RTP/RTCP包:
3.2.8.3 ACK消息的处理过程OpAck
3. 2. 8. 4 OpConfTargetOk多方会议检测:
3.2.9 呼叫等待
3. 2. 9. 1 呼叫等待的详细描述:
3. 2. 9. 2 操作之间存在的竞争
3. 2. 9. 3 呼叫中所涉及模块介绍
3.3 等待对方的呼叫
3.3.1 OpRing等待对方的振铃消息
3. 3. 2 OpStartRinging开始响铃
3. 3. 3 OpRingingInvite处理又一个INVITE消息(呼叫等待)
3. 3. 4 OpAnswerCall被叫打开媒体通道开始通讯
3.3.5 回到StateInCall状态
4.如何在改造现有的终端使之能传递视频流。
4.1一个H.261+的Codec的基本构造
4. 2 增加视频能力所需要做的工作

一.引言

  在各种的IP网络多媒体通讯协议中,当前在市场上占据主流位置的应当算是ITU的H.323和IETF的SIP两个协议,目前在单纯的话音市场,MGCP协议由于有大规模的用户扩容能力应用也正呈现上升的趋势,在2000年以前市场上占主流的主要是H.323协议,然而SIP协议由于它避免了复杂的原语(ASN.1)分析,它的应用也在2000年以后也得到高速的普及,甚至有超过H.323的趋势,成为H.323最有力的竞争对手,当然,由于SIP协议的一些固有缺陷(下面将会详细介绍这些缺陷),这种情况在未来的几年可能不会出现,不过对于中等规模的多媒体通讯业务(每小时接入60000门)应用而言,采用SIP不失为一个方便,快捷的开发策略。

  在各种的VOIP开放原码的开发项目中,Vovida的基于SIP协议的VoCAL(Vovida Open CommunucAtion Library)不仅仅是在基于SIP的开放原代码协议栈中是最为庞大而且完善的,甚至在所有的原码开放的多媒体通讯协议栈中同样也是完善而且全面的,目前发布的VOCAL1.4.0主要支持RFC2543,据称在新版本的Vocal1.5.0将支持RFC3261协议;Vocal提供了基本的SIP呼叫控制和切换,例如:用户注册和登记,呼叫初始化,修改呼叫特性,或者重新定义呼叫特性,终止呼叫;以及一些用户的基本呼叫特性:例如呼叫前转,呼叫等待,呼叫阻塞,呼叫转移,语音邮件等等。

  对于一个Vocal系统的用户而言,Vocal同样为其提供了以下的一些能力:

1. 通过Web来配置整个Vocal系统;
2. 使用SNMP网管来检测整个系统和呼叫组网的状态;
3. 可以定义一个用户的呼叫特性列表(相当于H.323系列中的H.450补充协议部分);
4. 授权检查;
5. 广告信息;
6. 基于RSVP的简单QoS保证。

  同时,VOCAL也提供了详细的文档和SDK包以给用户做二次开发,用户可以在C++,以及Call Processing Language(CPL),Java Telephony API上开发自己的应用。

二.H.323和SIP之间的差异:

  这一节和本文的内容似乎没有太大的关系,不过笔者认为作为市场主流的H.323和SIP之间需要做一个相关性的比较,以免使很多读者在选择协议的时候陷入歧途。

  虽然Vocal在SIP的应用上已经可以算是一个成功的实例,所以目前单纯以Vocal作为主体开发SIP的多媒体通讯系统从理论上是可行的,但是事实上目前所有的VOIP商业系统都是以H.323为主体,兼容SIP协议,似乎还没有一个厂商在实际上支持SIP(Cisco好象有类似的产品,不过应用前景似乎不是非常明朗),首先从市场上来看,H.323的系统已经有大量的投资,应用也非常普遍,SIP相对比较新,似乎不够成熟;从市场上来看,越来越多的附加服务将成为应用的主流,SIP领域相对来说比H.323能够提供更多,更灵活的服务,而且在信令的互通性上有更加多的优势,当然H.323也能够保证其他解决方案之间的互用性;但是,目前MGCP协议已经得到了大量的工业支持,简单的终端和更加复杂完善的呼叫控制方式让它得到了更多的应用,很可能会成为SIP的潜在竞争者。

  其次我们从ITU和IETF的条款保证上来看,IETF所制定的草案一开始都阐述一个让人失望的观点:本草案作为参考资料是不合适的,除非在"制定和完善中",这样在协议成熟和完全理解期间必然会把一些工作引入误区,特别是某些协议被更新和升级期间。相对而言,作为官方实体的ITU制定的协议一旦实施就不再轻易的改变,因此在发布协议前,已经对协议作了很长时间的互通性测试。

  最后从技术上来看,SIP和H.323在技术实现上有很大的不同:

  a.开发速度:SIP当然的优于H.323协议太简单了,不过如果H.323原语部分可以比较好的解析的话,事实上两者开发速度相差不大。

  b.多播:在这个方面IETF具有优势,有非常强大的应用经验的,SIP已经设计在很多多播的骨干网络上,h.323v1,v2要使用多单播同时进行的方式才能完成,不过H.323V3版本多播的支持就已经非常不错了。

  c.地址的运用上SIP使用Url上的机制非常灵活,这样可以让SIP以一种非常灵活的方式重定向到非SIP服务器上去,被另外一个SIP呼叫的SIP终端也能重定向到某个网页或者是电子邮件地址。对于H.323而言,命名的机制就非常混乱了,从ASN.1的文件我们可以看到有h323-ID,url-ID,transport-ID,email-ID,partynumber等等。

  d.对于SIP而言,所有的消息都采用文本编码,所以SIP消息非常简单,这样在开发的时候简单的网络检测就可以调试,反观H.323协议采用了PER或者BER的二进制编码方式,信令不是非常直观。

  e.系统资源的消耗上,SIP可以说是开销惊人,每次服务器发出通告的时候,都需要建立一个监听套接字,这样的结果势必造成大量的闲置套接字,假设在建立一个完整的Proxy/Register/RTP Gateway/三者和而为一的园区出口网关的时候,资源上势必会非常的紧张,这个是不能不予以考虑的问题。相反H.323在打开逻辑通道的情况下(OpenLogicalChannel消息)只建立一个套接字。

f. SIP没有会议控制能力,所以仅仅只能做到点对点的媒体通讯,而H.323一开始就考虑了会议功能,其中还包含了H.332会议控制协议。(Vocal提供了一个Conferencing Server可以做普通的会议控制)。

g. 基于无线的网络而言,H.323有很大优势,由于信令采用了二进制编码,所以比较适合手持设备实现,而SIP由于采用了文本方式就没有这样的能力。

三.本文的主要内容

  和RTH323的介绍一样,我在下面将会尽量详细的分析一下Vocal的整个原代码,当然不可能做到完全系统地向大家展示Vocal的精妙之处,其实我自己对这个协议栈还是有相当多不了解的地方,希望大家能对我们的研究提出宝贵意见,后续的文章将会以连载的方式对Vocal进行介绍,顺序如下:

3.1 Vovida User Agent:
3.2 Vovida Provision Server:
3.3 Marshal Server;
3.4 Redirect Server;
3.5 HeartBeat Server:
3.6 Policy Server:
3.7 CDR Server:
3.8 Network Manager:
3.9 Feature Server:
3.10 Translator Server:

  我们现在可以开始进入Vovida的第一个实体的介绍--User Agent


(点击放大)

User Agent

1.User Agent的简介:
  User Agent是描述一个普通的用户终端,用户代理,以下都简称UA端。本身来说UA端的代码在Linux或者是Windows上都可以编译运行。在Vocal中资料最详细是User Agent的介绍了,有关UA描述的所有的代码部分部分集中在\SIP\UA目录下面,SIP的Stack软件主要集中在\SIP\SIPSTACK,SIP消息和状态的基类描述主要集中在\SIP\BASE;大家如果对SIP的状态和命令不是非常熟悉的话,可以进入\SIP\UA\目录下浏览以下的几个线图:

1. UaOverView.gif:
  对于UA中全部的主要类的关系描述,主要是展现了一些比较重要的基类。
2. UaSimpleStatesComplet.gif
  UA端的一个简单的呼叫和应答的全部命令和状态的交互示意图。
3. Ua-States.gif
  UA状态迁移的示意图。
  另外在UA中我们会把基本的SIPStack的一些调用做一下详细的介绍,所以篇幅可能会比较长。

下面是一些所使用到的SIP基本的类的介绍:
  HeartlessProxy :创建了一个容纳呼叫的"容器",和SIP的消息堆栈,以及WorkThread和SipThread(用于对SIP消息的队列处理),由HeartlessProxy::Run()方法调用这写线程的Run方法,使他们启动。该类的初始化是用一个Builder的基本类对它进行实例化。

  BasicProxy:由源自HeartlessProxy,它让系统使用HeartBeat机制,在这个类中创建了三个HeartBeat类型的线程:HeartbeatTxThread,HeartbeatRxThread,HouseKeepingThread,不过暂时在Ua中都没有应用到,一般是用在HeartBeat Server中(注:HeartBeat的机制就是指在Vocal的Server集群通过多播端口向HeartBeat Server发送HeartBeat数据报,如果在指定的时间内没有收到该数据报,那么认为该服务器处于Down状态,由HeartbeatServer发送状态消息到SNMP网管,同时启动备份设备,这个机制类似于BGP,EIGRP协议中的后备路由方案)。

  SipThread:SipThread源自ThreadIf(Thread Interface),主要作用是接收并且在sipstack对所收到的SIP消息排队,并且对接收的SipMsg(SIP消息)产生相应的Sip本地处理事件SipEvent,并且把他们放置在一个Fifo队列中等待处理,SipThread::thread()是循环处理的线程。

  WorkerThread:和SipThread一样,它也是源自ThreadIf,主要作用在于接受并且列队处理上面SipThread收到的Sip本地处理事件SipEvent。

  Builder:是一个基本类,它由WorkerThread调用,在这个类中包含了针对用户代理的CallContainer(包含所收到的各种呼叫信息)类的指针,从代码上看Builder在HeartLessProxy/BasicProxy被创建的时候创建。

  Feature: Feature是一个状态(State)容器,用于装载各种状态,从所有的状态上来说,Feature是所有State的集合,Feature,State,Operator之间的关系是一种容器包含的关系,在Feature::process()中会调用State::process()来完成各个状态的处理,它会返回下一个在容器内要处理的状态(Sate)。.

  State: State是Operator的集合容器, State::process() 调用 Operator::process().和上面所描述的一样Operator返回在容器内的下一个操作(Operator)。

  Operator : Operator则是一个基本类,Operator::process() 是一个虚函数,他需要其他的Operator子类对它进行实例化,它实质上也是描述各种操作的一个基本类。

  SipProxyEvent: 这个是一个基本类,用于描述各种SIP的事件信息,包括各种SIP消息和各种本地的设备消息,同时它包含了SIP消息的输出队列指针,使用的时候,可以把他载入输出的FIFO中。

  SipEvent: 从SipProxyEvent上继承,用于描述各种SIP消息,使用中当SipThread收到一个SipMsg的时候创建一个SipEvent,同样SipEvent也会安装在输出的FIFO中(Outputfifo)。

  DeviceEvnet: 从SipProxyEvent上继承,用于描述本地设备事件。

  TimerEvent:从SipProxyEvent上继承,在设定的时钟超时的时候产生该事件。

  CallContainer: 是CallInfo类的容器类。

  CallInfo: 是一个基本类,用于对呼叫的各种信息的描述集合,任何一个SipProxyEvent都包含了一个CallInfo。

  CallProcessingQueue: 一个用于装载各种SipProxyEvent消息的FiFO队列,在构造HeartLessProxy的它被创建,WorkerThread对他中间的消息进行排队处理。

  FeatureThread:被Marshal Server调用用于发送接受subscribe/Notify消息对,得到合法的用户的列表和呼叫特性(在后面介绍marshal和Feature Server的时候会详细介绍)。

  ResGwDeviece: 所有设备的基本类,用于描述所有的设备,当然它中间的很多属性需要具体的设备进行实例化。


(点击放大)

下面介绍一下在UA端所使用到的基本类:

  UserAgent: 用于描述一个基本的用户代理,它通过Run方法启动以后打开了用户端的RTP通道和设备媒体设备处理进程,并且启动了HeartlessProxy的Run方法,开始启动SIP消息处理线程。

  DeviceThread: 用于处理各种媒体设备,以及输入输出设备的线程,将收到的设备消息放在CallProcessingQueue队列中。

  RTPThread: 用于处理RTP/RTCP会话。

  SubScribemanager: 用于MS端发送subscribe消息到RS端,以及接收Notify消息,处理用户的呼叫特性列表。

  UaCallContainer: 继承CallContainer类,主要在Ua端使用,定义了UA呼叫的各种信息的集合。

  SipTransceiver: SIP消息的发送和接收器的描述,包含有一个接收缓冲队列和发送缓冲队列。

  UaBuilder: Builder类在Ua端的描述,继承了Builder的各种描述,这个是UA端的一个重要的类,它负责构建各种SIP消息事件,并且在这里包含了UA的注册和各种状态机的初始化和实例化过程。

  UaConfiguration: CFG文件的描述类。

  UaCallInfo:属于CallInfo的子类,包含了UA的各种工作状态,可以让不同状态下的所有所有的Call操作(发送和接收),使用一个相同的状态机。

  RegisterManager:用于跟踪处理UA端的注册。

  LoadGenThread:检测线程,用于大量的呼叫时候对系统的检测.


(点击放大)

  其实这几个线图对UA的描述还是非常粗糙,如果大家对UA端的代码没有任何的阅读的话,看他们是完全看不懂的,这些只能是一些内部开发人员专用文档而已,下面我们开始对UA原代码部分做详细的介绍:

2.UA部分主要程序部分的介绍:

2.1 主程序:\SIP\UA\UA.cxx
  主程序部分主要是根据CFG文件中的定义建立本地的呼叫和等待接收进程。

main( int argc, char* argv[] )
{
… …
//是否把当前的UA设置为守护进程。由CFG文件确定。
if ( UaCommandLine::instance()->getBoolOpt( "daemon" ) )
{
// TODO set cpLog to use syslog
assert( Daemon() >= 0 );
}
… …
if (UaCommandLine::instance( ) -> getIntOpt( "retransmit" ))
{
SipTransceiver::reTransOn();
}
else
{
SipTransceiver::reTransOff();
}

if (UaCommandLine::instance()->getIntOpt("retransinitial") != 500 ||
UaCommandLine::instance()->getIntOpt("retransmax") != 4000 )
{
SipTransceiver::setRetransTime(
UaCommandLine::instance()->getIntOpt("retransinitial"),
UaCommandLine::instance()->getIntOpt("retransmax")
);
}
//这里是打开配置文件,我们在这里使用的配置文件暂时定为Ua1001.cfg(以下均相同)
const string cfgStr = UaCommandLine::instance()->getStringOpt( "cfgfile" );
FILE *cfgFile = fopen( cfgStr.c_str(), "r");
if ( cfgFile == 0 )
{
cerr << "can not open " << cfgStr << endl;
cerr << "Usage: " << argv[0] << " " << appUsage << endl;
exit( 0 );
}
else
{
fclose( cfgFile );
UaConfiguration::instance( cfgStr );
}
// if the config file has a log level, do something about it
if(UaConfiguration::instance()->getLogFilename() != "")
{
int retval = cpLogOpen(
UaConfiguration::instance()->getLogFilename().c_str());
if(retval == 0)
{
cpLog(LOG_ALERT, "Could not open %s",
UaConfiguration::instance()->getLogFilename().c_str());
}
}
//创建一个Uabuilder类,它在类状态图中的位置可以参考Uaoverview.gif中它的位置,
Sptr < UaBuilder > uaBuilder = new UaBuilder;
//在这里创建一个用户代理,我们要确定它在本地的侦听和发送的端口,我们同样从CFG文
//件中得到。
UserAgent ua( uaBuilder, Data( UaConfiguration::instance()->getLocalSipPort() ).convertInt() );
ua.run();
if ( UaCommandLine::instance()->getBoolOpt( "voicemail" ) )
{
//下面的两个Run我们暂时不定义,在SNMP网管的时候将介绍HearterBeat的时候在做//详细的阐述(所谓的HeartBeat技术是在多播口上定时发送heartbeat消息,以通知目前//端点的状态,类似于BGP协议中的Hello消息。
#if defined(HAS_VOICEMAIL)
cpLog( LOG_DEBUG, "UA is running as voicemail front end" );
if( !UaCommandLine::instance()->getBoolOpt("no_heartbeat") )
{
#if defined(HAS_HEARTBEAT)
// Create and start heartbeat transmit thread
Sptr < HeartbeatTxThread > heartbeatThread
= new HeartbeatTxThread(sipPort,
500,
(const char*)"226.2.2.5",
6000);
heartbeatThread->run();
heartbeatThread->join();
#endif
}
#else
cpLog( LOG_ERR, "UA is NOT compiled to run as voicemail front end" );
#endif
}
//加入UA端到本地的运行队列里面
ua.join();
return 0;
} // ua main()

  看完了UA的主程序,我们可以看到,目前在UA端的部分的主要工作就是创建一个User Agent的实体UserAgent然后调用Run方法让它运行,那么我们看一下UserAgent这个关键类的一些基本情况:


(点击放大)

2.2 创建一个User Agent的实体:

  在创建一个User Agent的实体的同时还有一个非常重要的实体HeartLesProxy,用于处理SIP的各种消息,并且开启后台工作线程;它的创建过程我们稍后做详细介绍。

UserAgent::UserAgent( Sptr uaBuilder, unsigned short sipPort, Data appName )
: HeartLessProxy( uaBuilder, sipPort, appName )
{
const char* useDevice = "NULL"; // default to NULL_HARDWARE
… …
//用什么声音设备?在这里我们暂时定为Sound Card好了,如果要是用Quicknet来集成
//各种压缩算法当然更好,不过价格也高了一些,下面都以Sound Card作为标准介绍。
if( UaCommandLine::instance()->getBoolOpt( "soundcard" ) )
{
useDevice = "SOUNDCARD";
}
//LOAD GENERATION是一个检测线程,可以在屏幕上打印各种命令的往复消息,以及系统的各//种统的状态。
if(! UaConfiguration::instance()->getLoadGenOn())
{
// Create devices only if load gen is turned OFF.
//设备实例化,并且向带入设备名称以及所要处理的消息队列,在实例化的过程中会//打开一个声卡设备,并且将这个声卡设备绑定两个输入的命令,输出命令的FIFO
//队列当中,(inputQ,和outputQ)详细可以参看SoundCardDevice的建构函数,它//阐述了如何绑定这两个队列inputQ,outputQ,并且初始化ResGwDevice(所有的声音//设备的父类)。
UaDevice::instance( useDevice, myCallProcessingQueue );
if( ! (strcmp(useDevice, "NONE") == 0) )
{
cpLog( LOG_DEBUG, "Create RTP Thread" );
//创建一个RTP包处理线程用于对RTP Packet的处理
rtpThread = new RtpThread( UaDevice::instance() );
assert( rtpThread != 0 );
}
//这里调用了sound Card的设备消息处理线程的创立,用于处理与声卡设备相关的各种消//息.
deviceThread = new DeviceThread( UaDevice::instance() );
assert( deviceThread != 0 );

cpLog( LOG_DEBUG, "Create SubscribeManager" );
Sptr subManager = new SubscribeManager( mySipStack );

if ( UaConfiguration::instance()->getSubscribeOn() )
{
cpLog( LOG_DEBUG, "Create Feature Thread" );
//这里建立一个向FS发送消息的线程,关于这个部分的内容在Feature //Server的部分再做详细介绍.
featureThread = new FeatureThread( subManager );
assert( featureThread != 0 );
uaBuilder->setSubscribeManager( subManager );
}
}
else
{
… …
}

// 是否打开重传机制?
if (UaCommandLine::instance( ) -> getBoolOpt( "retransmit" ) )
{
SipTransceiver::reTransOn();
}
else
{
SipTransceiver::reTransOff();
}

// 定义接收代理服务器(Proxy)发出的消息所储存的容器
myCallContainer = new UaCallContainer;
assert( myCallContainer != 0 );
//绑定容器到用户端
uaBuilder->setCallContainer( myCallContainer );
//设置SIP的消息堆栈
uaBuilder->setSipStack( mySipStack );
//开始向注册服务器发送注册(Register)消息。
uaBuilder->startRegistration();
}
2.3 HeartLessProxy的创建:
HeartLessProxy
(
const Sptr < Builder > builder,
unsigned short defaultSipPort,
Data applName,
bool filterOn,
bool nat,
SipAppContext aContext
)
{
myCallContainer = new CallContainer;

myBuilder = builder;
myBuilder->setCallContainer(myCallContainer);
//这里创建了一个消息的输出队列,在前面的创建一个UserAgent的实体的过程中已经
//阐述过会把它绑定到相关的设备上去
myCallProcessingQueue = new Fifo < Sptr < SipProxyEvent > >;
//这里创建一个WorkThread线程在该线程中的myBuilder->process(nextEvent)
//检查消息队列myFifo中的返回消息(调用Uabuilder->process进行检查),从而
//得到返回的消息。
//很明显,这里新创建了一个 myWorkerThread工作线程,我们等一下就会看到如何把它Run
//起来
myWorkerThread = new WorkerThread(myCallProcessingQueue, myBuilder);

//创建一个SIP消息收发器的实体,在这个实体的构建里主要是把收发SIP消息的TCP/UDP
//的收发通道创建(SipUdpConnection和SipUdpConnection)。同时会构造一个SNMP的
//SipAgent.他的主要作用是向SNMP网关发送SNMP消息,描述网络的运行状态
if ( filterOn == true )
{
mySipStack = new SipTransceiverFilter(applName, defaultSipPort, nat, aContext);
}
else
{
mySipStack = new SipTransceiver(applName, defaultSipPort, nat, aContext);
}
myBuilder->setSipStack(mySipStack);
//创建一个SIP消息的解析线程 。
mySipThread = new SipThread(mySipStack, myCallProcessingQueue);

… …
}

2.4 让User Agent Run起来:

  构建User Agent的工作已经完毕,现在应该让调用它的Run方法了;从下面的程序可以看到,Run方法的调用,让整个程序进入一种"Idle"的状态,等待命令输入和状态的产生,这个过程我们可以看到在Ua.CXX的Main程序中调用(ua.run())。

Void UserAgent::run()
{
//调用HeartLessProxy的Run方法,稍后做详细的介绍
HeartLessProxy::run();
… …
deviceThread->run(); //调用SoundcardDevice::hardwareMain(0)
… …
rtpThread->run();//调用SoundCardDevice::processRTP()进行RTP流的处理
… …
//在这里向FS发送队列(myQ = new Fifo < Sptr < SubscribeMsg > >)中的各种消息,不
//过在Ua1001.cfg中,参数Subscribe_on设置为OFF所以,本章我们对FS暂不予以分析,
//在最后一章详细分析FS的时候回着重分析它.
featureThread->run();//调用subscribeManager::subscribeMain()
… …
//后台监测线程开启.
loadGenThread->run();//调用LoadGenMonitor::lgMain()

// User TimerEvent to kick start the load generator
… …
} // UserAgent::run

(未完待续)

作者供稿 CTI论坛编辑

作者联系方法:[email protected]

在Vovida的基础上实现自己的SIP协议栈(二)