Resiprocate及SDP协议详细分析(共35页).doc
精选优质文档-倾情为你奉上Resiprocate介绍1.前言本文主要内容来自互联网,特此感谢Steven的辛苦撰写和resiprocate开源组织的无私奉献以及sip协议的创造者Schulzrinne教授和Rosenberg大师的辛勤工作。2从SIP谈起说明:不期待一次就把RFC3261或者其他的协议文档内容及其细节全部记住或者完全理解;把原理性的东西及其脉络厘清也许更重要;在调试程序和看协议栈源码的过程中我的做法是一直把RFC3261(经常的是那份中文文档J的文档打开;遇到忘记或者不是太明白的概念和内容就在文档中再搜索相关主题及内容来看看;经常会碰到这样的问题,我发个内容给SIPProxy或者SIPServer,可是并没得到我希望的回复或者与期待的回复内容有出入,这时,我的经常做法是再去研读协议的相关定义,看看是不是我哪个细节并没理解深入或者引起注意,导致我发出去的内容与协议标准有出入或者我的流程与协议定义不吻合。接下来的内容是前人的文档整理,只是个大概,如果没兴趣,完全可以跳过不看;协议栈部分基本上是分成DUM与Stack两部份可以先后看,也可以先看Stack部分。补充说明:文档中的大部分图片都来自网上公开的资料,只有少数几幅是自绘,因此出现内容不清和误导,概不负责J特此感谢借鉴资料和图片的原创者们,虽然他们并不知道又误导了一个菜鸟。2.1SIP(SessionInitiationProtocol)简介最先由美国哥伦比亚大学的HenningSchulzrinne教授在1998年初开始发起,1999年3月由IETF的MMUSIC(MultipartMultimediaSessionControl)工作小组制定正式标准成为RFC2543,1999年9月IETF成立新的工作小组,负责SIP新版本2.0的制定,并于20XX年7月释出初版RFC2543bis,于20XX年发布了RFC3261。RFC3261的发布,标示着SIP的基础已经确立,随后又发布了几个RFC增定版本,充实了安全性及身份认证等几个领域的内容,例如RFC3262对临时响应做了可靠性的规范。RFC3263确立了SIPproxy的定位规则。RFC3264提供了Offer/AnswerModel,RFC3265则是确立了具体的事件通知。如同Internet一样,SIP易于理解、扩充、及实做,作为IETF的规范,SIP将Internet开放标准的精神延伸至通讯领域,实现了不同计算机、电话、及软件的通讯。SIP的讯息类似于HTTP(RFC2068),其寻址方式,则是重用了SMTP的寻址方式,SIPaddress(如:sip:inabassl.es.ncku.edu.tw)与E-mailaddress的结构相同,SIP甚至利用Web的体系结构,如DNS,而使得SIP的使用者之间的通讯,有更高的扩充性。Ø SIP有下列几点特性Ø 利用文字(Text-based)的方式来编码,类似HTTP/1.1Ø Client-Server的架构ü Clients端初始一个呼叫(caller)ü Servers端响应呼叫(callee)Ø Signal与media独立,SIP负责signal部分,media传送部分可以使用RTP等,可与其它IETF所制订的协议配合,例如:RFC2327(SDP),RFC2616(HTTP/1.1),RFC2396(URL)2.2SIP命名方式SIP的地址称做SIPURLs,其格式为userhost:port,使用者利用REGISTER的SIPrequest来结合自己的SIPURLsuser代表使用者名称或是电话号码host代表domainname或是数字型态的IPaddress举例来说sip:inaba或是sip:140.92.61.552.3SIP组件介绍在SIP是一个ClientandServer的架构,在此环境当中,有三个主要的组件分别为:UserAgents,Servers还有LocationServers。Ø UserAgents在SIP环境中是终端设备,主要负责产生SIPrequests,用来建立多媒体会议(mediasession),并且传送及接收多媒体资料。UserAgents又分成了UserAgentClient(UAC)及UserAgentServer两种模式。UAC负责产生一个Request及处理一个Response,UAS怎是接受一个Request并且产生response。在Session建立过程中,UA通常需要接替着扮演这两个角色。这点并不像其它ClientandServer架构,例如HTTP,PC一直扮演着HTTPclient的角色,而WebServer也一直扮演着HTTPServer的角色。Ø Servers根据RFC3261中定义,Server主要分成了Proxy,Redirect,以及Registrarserver。l SIPproxy:负责接受UA或其它proxy所发送的SIPRequest,并且转送Request到其它地方。l RedirectServer:负责接受UA或其它proxy所发送的SIPRequest,并且传回redirectionresponse(3xx),指出这个Request应该送往何方。l RegistrarServer:负责接受SIPregistrationrequests,并且更新SIPUA在LocationServer或其它数据库当中的信息。SIPproxy,Redirect还有Registrarservers只有做单纯的signal转送,他们没有传送media及产生SIPRequest的能力。Proxy,Redirect,以及Registrarserver只是逻辑概念定义的不同而物理实现上完全可以在同一物理位置实现。l LocationServers:在RFC3261中,通常当作一个数据库来使用。数据库当中可以存放使用者的信息,例如URLs,IPaddress,或是其它资料等等。SIPUA不能直接来存取Locationserver,而是透过proxy,redirect,或是registrarserver。2.4SIPmessageSIPmessage的语意及表头与HTTP/1.1(RFC2616)相同。可以分成两类,一类是Request,另外一类是Response。在RFC3261当中,定义了六个基本的SIPrequest种类,如表2.1所示。方法说明INVITE建立会话SessionACK对INVITE做最后的确认BYE结束一个已经存在的会话CANCEL取消尚未建立联机的会话REGISTER注册使用者的URLOPTIONS查询Server及其功能表2.1、SIPmethodsResponse用status-codes来表示响应的内容,符合且扩展了HTTP/1.1responsecode。分成Provisional(暂时)及Final(最终)两类,Provisional为1xx系列,Final则包括了2xx,3xx,4xx,5xx,6xx等系列,表2.2表示各个不同类别的Response。方法说明1xxInformational2xxSuccessful3xxRedirection4xxClient-Error5xxServer-Error6xxGlobal-Failure表2.2、SIPResponse1xxInformationalresponses指的是server或是proxy正在执行一些未来的动作,并还没有一个定义好的response。2xx代表这个request是成功的,并且必须结束一个搜寻的动作。3xxRedirection会回复欲通讯的使用者目前的位置信息。4xxresponse是Server对于UAC所提出的request回复一个失败的response。5xxresponse是代表Server本身发生了错误。GlobalFailures6xx指的是server有一个关于特定的使用者最终的信息,不仅仅是这个特定的Request-URI,所有未来的对这个使用者的搜寻都应该被终止。表2.3为几个常用的Responsecode范例ResponsecodeReasonPhrase100Trying200OK302Movedtemporarily403Forbidden503ServiceUnavailable600Busy表2.3、SIPResponsecode2.5SDPSIPmessagebody当中,可以携带任何的资料,但通常是给通讯双方用来协商session相关的信息。SIP本身并没有提供多媒体协商的能力,多媒体协商必须仰赖SessionDescriptionProtocol(SDP),SDP本身并不是一个通讯协议,它的描述语言是基于文字的。SIP利用answer/offer的模式来使用SDP。呼叫者送出一个INVITE讯息并携带着SDP,其中SDP包含了呼叫者想使用的多媒体的格式、地址、Port,被呼叫方在响应的时候,便可以针对呼叫者所提出的SDP,做出接受或拒绝的响应,这种双方协商的结果,就可以得知多媒体资料格式及通讯的地址为何。SDP定义在RFC2327当中,后来经过修订及汇集draft之后,发表了RFC3264“AnOffer/AnswerModelwithSDP”,RFC3264明确的描述应该如何将SIP与SDP一起使用。SDP简单地提供一个描述session信息的格式。基本而言,一个session是由数个mediastream所组成,因此,要描述一个session必须要有数个相关的参数。SDP当中有session-level与media-level的参数,Ø session-level的参数包含了ü session的名称、ü session的发起者、ü session的期限等等。Ø Media-level的信息包含了ü media的种类,ü portnumber、ü 传输协议以及media的格式。图2.1为SDP的结构。SessionDescriptionSessionLevelProtocolVersionOriginatorandSessionIDSessionNameSessionTimeMediaNameandTransportConnectionInformationMediaDescription图2.1SDPsessionDescriptionStructure2.6SIP运作模式SIP的呼叫建立如图2.2所示,开始的时候caller会送出SIPINVITE的讯息给callee,callee收到之后,会马上响应一个100Ringing的讯息通知caller,说明目前callee已经收到INVITE讯息且正在处理中,如果callee愿意与caller通话,便会送出200OK的response,最后caller再送出ACK,此时便可以开始进行会议。当其中一方想结束通讯时,便会送出BYE的讯息来通知对方,对方响应200OK便可以结束目前进行会议。Client(Caller/UA)Server(Callee/UAS)INVITE100:Trying180:Ringing200:OKACKBye200:OKRTP图2.2SIPcallflow2.6.1SIPproxy模式以图2.3为例,caller(inaba)先送出一个INVITE讯息呼叫callee(yucho),proxyserver收到之后便会去做查询,查询完成之后便得知目前callee实际的地址在yucho,于是proxyserver便会以yucho为对象发出INVITE讯息。callee在回复200OK的时候,会将200OK的response响应给proxyserver,再由proxyserver转送给caller。inabayucho1.INVITE yucho4. ResponseSIP Proxy2. INVITEyucho3. Reponse2.6.2SIPRedirect模式如图2.4所示,与proxyserver不同的地方,在于redirectserver查询得知callee实际的地址的时候,并不像proxyserver会直接代为处理之后session的建立,而是将callee的实际地址告知caller,让caller自行送出新的INVITE。使用redirect的模式,可以减低server的负担,但caller必须有能力将request的讯息传送到callee。inabayucho1.INVITE yucho2. Move temporally Contact: yuchoSIP Proxy/Redirect Server3. INVITEyucho4. Reponse3.关于协议栈的系统设计SIP为应用层(Application-Layer)的协议,所以不需要改变操作系统便可以支持,以SIP来支持行动通讯,对于real-time的服务可以提升其效能。SIP已经获得3GPP(ThirdGenerationPartnershipProject)、3GPP2(ThirdGenerationPartnershipProjectNumber2)等机构认证,成为未来第三代行动通讯(3G)的标准。事务用户层(Transaction User)事务层(Transaction )传输层(Transport)语法与编码层(Syntax and Encoding)下面是SIP的分层图示,IETF坚持分层,不同模块功能相对独立,各层之间松散耦合。以下是Sip客户、服务端数据传输示意图:Sip Server/ProxySIP ClientSIP ClientSIP/PSTN G/WIPGateway/Firewall3.1ResiprocateSIPStack解析之基础类最期望的还是能把Resiprocate的源码脉络能理的清楚,大家能看个明白;并且还想搞清楚它为什么要这样设计,它设计的理念和考虑的关键是些什么。接下来的介绍其中有个人对源码设计的粗浅认识,其间会夹杂作者对面向对象设计的胡乱发挥。首先还是要祭出这面大旗,”类是对概念的描述,面向接口编程;封装变化。”Resiprocate中大部分类就是对RFC3261各种SIP元素、组件的封装,并且也体现了RFC协议设计的层次。在面向对象的设计中我们首先就要厘清问题域的所在;SIPStack的设计就是要充分考虑完整展现RFC定义的各种元素和概念以及让这些独立而又关联的元素互动起来成为一个活的系统。可以这样来考虑,比如我们知道RFC定义了一个SIPMESSAGE的概念;下面是从RFC文档拷贝的内容:SIP消息=起始行G消息头部CRLF(空行)消息体因此SIPMessage这个概念元素还包括了更多的元素和概念;SIPMessage中我们能抽象出更通用的概念我们暂且叫它Message;起始行的概念E文RequestLine以及SattusLine又包括了很多消息头(这是包容的关系),SIPURL也包括消息头,等等,还有什么参数什么的元素呢;当我们在考虑和提炼这些概念和元素的时候,我们思考怎么抽象他们呢,它们又有什么基本的元素及其共性呢?他们之间的关系如何组织呢?Resiprocate的源码告诉了我们如何去设计和封装这些概念的上佳实现。在Resiprocate中一些RFC3261中定义元素的对应:建议:利用CRC卡片的方式去记录理解Resiprocate中的大量的类及其关系。CRC:类、关系、控制。3.1.1DUM的设计浅析:抽象接口:CLASSHANDLED,CLASSInviteSessionHandler(诸如此类)对象之源:CLASSHANDLED(多态和控制的基础)交互控制:CLASSHandle,CLASSHandleManager概念封装成类典型:CLASSDialog,CLASSDialogId,CLASSDialogSet,CLASSDialogSetId,CLASSInviteSession.Utility工具类:CLASSBaseCreator,CLASSDestroyUsage,CLASSProfile流动之源:DialogUsageManager:process(),Dialog:dispatch(constSipMessage&msg)状态机的位置:DialogUsageManager:incomingProcess,DialogSet:dispatch,Dialog:dispatch这幅图期待加深对DISPATCH的理解:3.1.2DUM部分设计的理解:观察者模式,工厂模式(大量容器的使用也是一种变体如:DialogSet),代理类句柄类(隐藏实现),句柄和代理的一个特点就是重载了operator->、operatorG等。TGoperator->()returnget();constTGoperator->()constreturnget();T&operator->()returnGget();constT&operatorG()constreturnGget();Handled:Handled(HandleManager&ham):mHam(ham),mId(Handled:npos)mId=mHam.create(this);StackLog(<<"&&&&&&Handled:Handled"<<mId<<"this("<<this<<")"<<&ham);Handled:IdHandleManager:create(HandledGhandled)mHandleMap+mLastId=handled;/typedefHashMap<Handled:Id,HandledG>HandleMap;/HandleMapmHandleMap;returnmLastId;其他:Transaction(TransactionState之类),下面这幅图不完全针对Resiprocate,只是觉得它的层次可以帮助理解:这幅图的表达有借鉴理解的价值:SIPUAFiniteStateMachine最完整的体现在:voidTransactionState:process(TransactionController&controller)3.2ResiprocateSIPStack解析之系统架构3.3ResiprocateSIPStack解析之UsaerAgent实例利用Resiprocate实现UA需要实现一个Sip客户服务端代理实现类来和UI等交互及完成事件回调控制。要处理sip的一系列事件,必须继承相关事件句柄控制接口,它们都是一些抽象类,如:InviteSessionHandler会话的事件接口ClientRegistrationHandler注册登陆事件接口。继承这些接口,说明客户端将要响应相关事件,一般而言肯定要组册和会话,因此上面的两个事件接口是一定要继承和实现的。因此一般来说sip客户代理类要管理和实现事件抽象接口。见相关例子。DialogUsageManager是sip事务层管理类,是一个大总管的角色;看其Makexxx系列的函数能明白它能发起一系列登陆、会话邀请的动作及其回复。因此这样的Sample就完成了一个会话邀请:NameAddruacAor;被邀请方的SipUri地址uacAor.uri().scheme()="sip"uacAor.uri().user()=Data(“”);sip的注册帐号uacAor.uri().host()=Data(“”);注册服务器的URL或者IP地址/接下来就是一通初始化工作,应该能看明白/setupUACSipStackstackUac;DialogUsageManagerGdumUac=newDialogUsageManager(stackUac);dumUac->addTransport(UDP,120XX);SharedPtr<MasterProfile>uacMasterProfile(newMasterProfile);auto_ptr<ClientAuthManager>uacAuth(newClientAuthManager);dumUac->setMasterProfile(uacMasterProfile);dumUac->setClientAuthManager(uacAuth);TestUacuac;dumUac->setInviteSessionHandler(&uac);dumUac->setClientRegistrationHandler(&uac);dumUac->addOutOfDialogHandler(OPTIONS,&uac);auto_ptr<AppDialogSetFactory>uac_dsf(newtestAppDialogSetFactory);dumUac->setAppDialogSetFactory(uac_dsf);/youraor,credentials,etcheredumUac->getMasterProfile()->setDigestCredential(uacAor.uri().host(),uacAor.uri().user(),uacPasswd);dumUac->getMasterProfile()->setDefaultFrom(uacAor);dumUac->getMasterProfile()->setDefaultRegistrationTime(70);dumUac->send(dumUac->makeInviteSession(uasAor,uac.sdp,newtestAppDialogSet(GdumUac,"UAC(INVITE)");/如此就生成了SIP会话邀请的消息发了出去。参考代码:BasicCall.cxx/类似的登录注册更简单:SipMessage®Message=dumUac->makeRegistration(uacAor,newtestAppDialogSet(GdumUac,"UAS(Registration)");dumUac->send(regMessage);然后就交给这样的一个后台处理流程去处理吧:unsignedlong_stdcallUacThreadFunction(voidGarg)UaAgentGuac=(UaAgentG)arg;resip:DialogUsageManagerGdumUac=uac->mDum;resip:SipStackGm_hSipStack=uac->mStack;for(;)FdSetfdset;m_hSipStack->buildFdSet(fdset);interr=fdset.selectMilliSeconds(resipMin(int)m_hSipStack->getTimeTillNextProcessMS(),10);ASSERT(err!=-1);m_hSipStack->process(fdset);while(dumUac->process();return0;在Dum(DialogUsageManager)的类中基本上这样一条线:DialogUsageManager产生DialogSet,DialogSet产生Dialog,Dialog产生InviteSession;InviteSession又分ClientInviteSession和ServerInviteSession。Dum部门还定义了很多句柄管理类,通过它我们能得到真实的对象,从而完成操作,这在事件响应中很有用,如:virtualvoidonNewSession(ServerInviteSessionHandlesis,InviteSession:OfferAnswerTypeoat,constSipMessage&msg)sis->provisional(180);/sis是ServerInviteSession的句柄实例,通过它我们得到ServerInviteSession对象,从而完成相关动作。/。3.4ResiprocateSIPStack解析之消息流程不管是SendMessage或者对RecvMessage都是把它首先放进先进先出的一个队列。给张图展示下先:看看Transport(Transport.hxx)类中的成员变量就是它们的归属:Fifo<SendData>mTxFifo;/ownedbythetransportFifo<Message>&mStateMachineFifo;/passedin在整个Resiprocate大家族中事务层概念的体现是TransactionUser类,而其真正的实例和管理类就是DialogUsageManager;从其:classDialogUsageManager:publicHandleManager,publicTransactionUser能看出来;HandleManager点出了DialogUsageManager的管理功能的本质,并且管理各种对象(Handle就是各类对象的句柄,关于后面会提到)。首先要明白在整个Resiprocate系统中不管是我们发出或者收到的SIPMessage都是放进了先进先出的队列然后不断轮询处理,这有点点点象Windows的消息系统,对应收发的消息Resiprocate提供事件通知的机制。MessageFiFo的大致流动走向如此:Outgoingmessages的源码流动走向大致如下,以下是INVITEMESSAGE产生及其流动示例:DialogUsageManager:makeInviteSession-àDialogUsageManager:sendàDialogUsageManager:sendUsingOutboundIfAppropriateàSipStack:sendTo-àTransactionController:send(在此处被添加进了FiFo-mStateMacFifo.add)这之后就是等待被处理(TransactionController:process)/newsipmsgfromtheTU/注解一下两个函数名sendToWire这是往外发送,发到线上去就是往网线上朝外发/啦,sendToTU就是往TransactionUser送往事务层发啦一般是接收到的SipMessgae/从Transport往上递交。以发起一新的INVITE所进行的后台Process流程为例:voidTransactionState:process(TransactionController&controller)àvoidTransactionState:processClientInvite(TransactionMessageGmsg)àvoidTransactionState:sendToWire(TransactionMessageGmsg,boolresend)à下面摘录一下TransactionState:sendToWire(TransactionMessageGmsg,boolresend)中的部分源码注释一下:mDnsResult=mController.mTransportSelector.dnsResolve(sip,this);/这一部/分就是整个源码中Ares上场表演的时候了,这部分是DNS处理的实现,在我的移植/中我把这部分简单改写成了调用windows的dns函数如gethostbyname/gethostname/系列。handle(mDnsResult);voidTransportSelector:transmit(SipMessageGmsg,Tuple&target)à再给段transmit中源码:Data&encoded=msg->getEncoded();encoded.clear();DataStreamencodeStream(encoded);msg->encode(encodeStream);encodeStream.flush();target.transport->send(target,encoded,msg->getTransactionId();voidTransport:send(constTuple&dest,constData&d,constData&tid)voidInternalTransport:transmit(constTuple&dest,constData&pdata,constData&tid)SendDataGdata=newSendData(dest,pdata,tid);mTxFifo.add(data);最后到这儿就出去了(以UdpTransport为例):voidUdpTransport:process(FdSet&fdset)/只列举关键代码if(mTxFifo.messageAvailable()&&fdset.readyToWrite(mFd)std:auto_ptr<SendData>sendData=std:auto_ptr<SendData>(mTxFifo.getNext();constsockaddr&addr=sendData->destination.getSockaddr();intcount=sendto(mFd,sendData->data.data(),sendData->data.size(),0,&addr,sendData->destination.length();流程图:Incomingmessages的源码流动就简单一些,收的处理是被动的,因此是在Process中后台接收然后上传:看看UdpTransport:process最后把接收到的Buffer组织成SipMessage后放到了哪里:stampReceived(message);mStateMachineFifo.add(message);还是放到了Fifo<TransactionMessage>&mStateMachineFifo;/passedin这个先进先出队列中。我们看看接下来的几个函数就可以看到各个类里面的先进先出对列是如何关联起来的,也能明白上面的接收队列后来会在哪些地方会被处理到:TransactionController:TransactionController(SipStack&stack,boolstateless):mStack(stack),mStateless(stateless),mRegisteredForTransactionTermination(false),mDiscardStrayResponses(true),mStateMacFifo(),/这个地方mTuSelector(stack.mTuSelector),mTransportSelector(mStateMacFifo),/TransportSelector中的mStateMacFifo这/样就和TransactionController中的mStateMacFifo代表了同一个对象mStatelessHandler(Gthis),StatelessIdCounter(1),mShuttingDown(false)再看看这儿(此处代码只是节选没有完全列出):voidSipStack:addTransport(TransportTypeprotocol,intport,IpVersionversion,constData&ipInterface,constData&sipDomainname,constData&privateKeyPassPhrase,TransportProcessApproachthreadApproach)InternalTransportGtransport=0;Fifo<TransactionMessage>&stateMacFifo=mTransactionController.transportSelector().stateMacFifo();/看看,都是引用/类型,导致它们都指向了同一个先进先出的队列。tryswitch(protocol)caseUDP:transport=newUdpTransport(stateMacFifo,port,version,ipInterface);/注意传引用的stateMacFifo,导致TransactionController:/stateMacFifo其实和UdpTransport:stateMacFifo指向同一处。break;caseTCP:transport=newTcpTransport(stateMacFifo,port,version,ipInterface);break;default:assert(0);break;上面所列举的源码是为了引出下面这一段,这儿是接收和发送SIPMessage的后台处理的一一个Process;搜搜整个源码有好几个Process,正是这一串Process让各种SIPMessage在各层和各种类型实例间流动了起来:voidTransactionController:process(FdSet&fdset)