RFC3261(12 对话)
一个UA的核心概念就是对话。对代表了两个用户代理(UA)之间的持续一段时间的点对点的SIP关系。对话(Dialog)使得用户代理之间的消息顺序传递和两个用户代理之间的请求正确路由更加容易。对话(Dialog)可以认为是对SIP消息解释的上下文关系。第8节讲述了方法无关的UA处理和响应对话(Dialog)外的请求。本节将讨论如何通过请求和应答来创建一个对话(Dialog),并且在对话(Dialog)中如何发起和响应后续的请求。
一个对话在参与对话的UA中都有一个dialog ID作为标记,这个ID由Call-ID、一个本地tag和远程tag组成。各个UA的dialog ID在对话中是不一样的。特别是,在一边UA的本地tag,在另外一方就是远程tag。这些tag都是互相不透明的,并且使得整个dialog ID是唯一的。
dialog ID与所有的To头域中包含了tag参数的请求及应答相关。填写一个消息中的dialog ID的规则依赖于SIP元素是UAC还是UAS。对于UAC来说,dialog ID中的Call-ID的值会填写到消息中的Call-ID域中,远程tag放在消息中的To的tag参数中,本地tag放在From的tag参数中。(这些规则对请求和应答都适用)。对于UAS来说,dialog ID的Call-ID值放在消息的Call-ID头域中,远程tag放在From头域的tag中,本地tag放在To头域的tag参数中。
一个对话包含一些特定的状态用于以后的对话中的消息传送。这个状态由dialog ID,本地序列号(用来排序UA到对方的请求),远程序列号(用来排序请求从远端到本UA),本地URI,远端URI,远端target,一个布尔类型标记”secure”,路由集合(一组有序的URI)组成。路由集合由发送请求到对方需要途径的一组服务器列表组成。一个对话可以处于”early”状态,这是由于当这个对话收到了临时应答而创建,并且当收到了2xx终结应答的时候转换到”confirmed”状态。对于其他应答,或者没有应答,”early”对话将会终结。
12.1 创建一个对话
对话是由对一组特定请求的没有失败的应答来创建的。在本规范中,只有包含To tag的2xx和101-199应答,并且请求是INVITE时,会建立一个对话。当收到一个非终结应答的时候,对话会建立成”early”状态,并且成为early dailog。创建对话的时候可以使用Extension来定义扩展。13节描述了INVITE请求的更多细节。在这里,我们描述与方法无关的对创建对话状态的处理。
UA必须按照下边描述的方法对dialog ID进行赋值。
12.1.1 UAS行为
当UAS响应一个请求给出一个应答,并且这个应答会建立一个对话的时候(比如对INVITE的2xx应答),UAS必须拷贝请求中的所有Record-Route头域到应答中去(包括URI,URI参数,和其他任何Record-Route头域的参数,无论UAS是否识别的参数都需要原样拷贝),并且必须维持这些参数的顺序。UAS必须增加一个Contact头域给应答。这个Contact头域包含一个UAS在后续对话请求中接收请求的地址(这个包含了给INVITE请求的2xx应答的ACK请求处理的地址)。通常情况下,UAS会用IP地址或者FQDN形式来发布自己的这个Contact地址。这个在Contact头域中的URI必须是一个SIP或者SIPS URI。如果创建对话的请求在Request-URI中包含的是SIPS URI,或者在Record-Route头域的最上的一个值是SIPS URI,或者如果请求中没有Record-Route头域但是请求中的Contact头域是SIPS URI,那么给出的应答中的Contact头域必须是一个SIPS URI。 这个URI应该是全局有效的(就是说,这个URI可以用于对话外的消息)。同样的,在请求INVITE中的Contact头域的URI也不应当仅限于这个对话中使用。因此它可以用于对话外的消息中。
UAS接着创建这个对话的状态。对话状态必须维持直到对话结束。
如果请求是通过TLS过来的,并且Request-URI包含一个SIPS URI,”secure”标志将被赋值成为TRUE。
路由集合必须设置成为请求中的Record-Route的URI列表,保留所有的URI参数和顺序。如果请求中没有Record-Route头域,那么路由集合必须设置成为空。这个路由集合,即便是空的,为了以后的对话中的请求,也要覆盖任何预先存在(pre-existing)的路由集合。remote taget必须设置成为请求的Contact头域中的URI。
远程序列号必须设置成为请求中的Cseq头域的序列号。本地序列号必须设置成为空。dialog ID中的呼叫标志应该设置成为请求的Call-ID头域的值。dialog ID的本地tag必须设置成为对请求的应答包中的To头域的tag,并且dialog ID的远程tag必须设置成为请求中的From 头域中的tag。UAS必须能够处理接收到的请求中的From头域没有tag标志,在这种情况下,这个tag就是空值。这是为了兼容RFC2543协议,它并没有定义From tag。
对端URI(remote URI)必须设置成为From头域中的URI,并且本地URI必须设置成为TO头域中的URI。
12.1.2 UAC行为
当UAC发出一个请求,这个请求能够建立一个对话(比如这个请求是INVITE),它必须在Contact头域中提供一个基于全局的SIP或者SIPS URI(例如,可以在对话外使用的SIP URI)。如果请求包含一个Request-URI或者Route头域的第一个值是SIPS URI,Contact头域也必须包含的是SIPS URI。
当一个UAC接收到应答,并且这个应答建立对话的时候,它也同样构造这个对话的状态。这个状态必须维持到对话的结束。
如果这个请求是基于TLS发送的,并且Request-URI包含一个SIPS URI,那么”secure”标志被设置成为TRUE。
路由集合必须设置成为应答中的Record-Route头域的URI列表,保留所有的URI参数和顺序。如果在应答中没有Record-Route头域,那么这个路由集合必须设置成为空集合。这个路由集合即便是空的,为了以后的对话中的请求,也要覆盖任何预先存在(pre-existing)的路由集合。remote taget必须设置成为应答中的Contact头域的URI。
本地序列号必须设置成为请求中的Cseq头域的序列号。远程序列号必须设置成为空(它会由远端的UA在对话中发送请求而建立)。dialog ID中的呼叫标志必须设置成为请求的Call-ID头域的值。dialog ID的本地tag必须设置成为请求中的From头域的tag,dialog ID的远程tag必须设置成为应答中的To头域的tag。UAC必须能够处理接收到的应答的To头域中没有tag的情况,在这个情况下,tag值取值成为空。这是为了能够向下兼容RFC2543,它没有规定To的tag。
remote URI必须设置成为To头域的URI,local URI必须设置成为From头域的URI。
12.2 对话中的请求
当两个UA之间的对话建立以后,他们都可以在对话中初始化一个新的事务(transaction)。如果UA发送请求,将遵循UAC的事务规则。UA接收请求将遵循UAS的规则。在建立对话的事务过程中,UA扮演的角色可能是不一样的。
在对话中的请求可以包含Record-Route和Contact头域。不过,虽然他们会修改remote target的URI,但是这些请求也不会导致对话的路由集被改变。明确的说,如果请求不是刷新target的请求,那么这个请求不会更改对话的remote target URI,如果请求是刷新target的请求,那么这个请求才会更改对话的remote target URI。对于用INVITE建立的对话来说,唯一的能够刷新target的请求就是re-INVITE(见14节说明)。可能会有其他扩展定义通过其他方法来刷新target的请求。
注意ACK不是一个刷新target的请求。
刷新target请求只会更改对话的remote target URI,并且更改由Record-Route指定的路由集合。如果更新路由集合会带来严重的和RFC2543向后兼容问题。
12.2.1 UAC行为
12.1.1.1 产生请求
在对话中的请求是通过用许多对话的状态部分来构造的。在TO头域中的URI部分必须设置成为对话状态中的remote URI。To头域的tag参数必须设置成为dialog ID中的remote tag部分。请求的From URI必须设置成为对话状态中的local URI。From头域的tag参数必须设置成为dialog ID的local tag部分。如果remote或者local tag是空值,那么tag参数必须分别从From或者To头域中去除。
后续请求采用原始请求的To和From头域的URI,是为了向下兼容RFC2543协议的,在RFC2543协议中,使用URI作为对话的标志。在本规范中,只有tags用于区分对话。有可能在本协议的后续版本中,在对话中的请求必须强制反应原始请求的To和From头域的URI将会去除。
请求的Call-ID必须设置成为对话的Call-ID。在对话中的请求必须严格遵循单个递增的Cseq序列号(每次增加1)(当然要除了ACK和CANCEL,这两个请求中的Cseq必须和原始的请求或者确认请求一样)。因此,如果本地序列号不为空,那么本地序列号码必须依次增加1,并且这个数值要存放到Cseq头域中。如果本地序列号码是空的,那么在8.1.1.5节约定的初始值必须填写进去。在Cseq头域中的method字段必须和请求的方法(method)一致。
通过使用32位的长整数,使得即使每秒种产生1次请求,也会要136年才会用完这个整数出现重复。这个序列号的初始值的选取是为了让对话中后续的请求序列号不会重复。非0的初始值可以考虑采用时间来作为初始的序列号。一个客户端可以用31位有符号整数或者32位无符号整数来存放时间作为初始化的序列号。
UAC使用remote target和路由集合来构造请求中的Request-URI和Route头域。如果路由集合是空的,那么UAC必须把remote target URI放到Request-URI中,并且UAC不能添加Route头域到请求中。
如果路由集合不为空,并且路由集合的第一个URI包含lr参数(见19.1.1),那么UAC必须填写remote target URI到Request-URI,并且必须包含Route头域,这个Route头域按照顺序填写路由集合和路由集合的参数。
如果路由集合不为空,并且路由集合的第一个URI没有包含lr参数,那么UAC必须把路由集合的第一个URI放在Request-URI中,并且拆去所有不被Request-URI允许的参数。UAC必须增加一个Route头域顺序包含所有剩下的路由集合元素,及其参数。UAC接着必须把remote target URI放在Route头域的最后一项。
例如,如果remote targe是: sip:user@remoteua 并且路由集合包括:
<sip:proxy1>,<sip:proxy2>,<sip:proxy3;lr>,<sip:proxy4>
那么请求应该有下列的Request-URI和Route头域
METHOD sip:proxy1
Route: <sip:proxy2>,<sip:proxy3;lr>,<sip:proxy4>,<sip:user@remoteua>
如果路由集合的第一个URI不包含lr参数,那么说明proxy并不能支持本规范所约定的路由机制,而是支持RFC2543文档所约定的路由机制,那么在发送信息的时候需要通过替换Request-URI为接收到的第一个Route头域的值。将Request-URI的值放在Route头域的目的是为了保护Request-URI,使得它经过严格路由的时候不丢失(当请求遇到一个松散路由的时候会被返回到Request-URI中。)
在对话内的任何一个刷新target的请求中,都应当包含一个Contact头域,并且这个URI除非有必要,否则都应当是和对话内上次请求的URI值一样。如果”secure”标志设置成为TRUE,那么URI也应当是SIPS URI。
如同在12.2.2节讨论的那样,在刷新target请求中的Contact头域会更新remote target URI。这个允许UA提供一个新的联系地址(Contact address),表明它在对话中改变了自己的地址。不过,如果请求不是刷新target的请求,那么不会影响对话中的remote target URI。请求中的剩下的部分请按照8.1.1节描述的填写。一旦请求被创建了,请求将按照对话外请求发送标准步骤(8.1.2节)来解析服务器的地址并且发送请求。
8.1.2节中的步骤一般把请求发送到Route头域的最上一个地址,或者如果没有Route头域,那么就发送到Request-URI地址。由于受到特定的限制,这些步骤也允许把请求发送到另外一个地址(比如在route set中没有的缺省的外发proxy)
12.2.1.2 处理应答
UAC将会从transaction层收到请求的应答。如果客户端的事务层返回一个超时,这会等同于一个408(请求超时)的应答。UAC在处理对话内请求的3xx应答的时候,跟处理对话外的处请求是一样的。这个方法在8.1.3.4节中描述。需要注意的是,虽然UAC会尝试新的地址(处理3xx应答的时候),但是它依旧使用对话内的路由集合来构造请求的Route头域。
当UAC收到一个刷新target请求的2xx应答的时候,如果对话的remote target URI存在,那么它必须用这个应答的Contact头域的值来替换对话的remote target URI。
如果对话的请求的应答是481应答(呼叫/事务不存在Call/Transaction Does Not Exits)或者一个408(请求超时),那么UAC应当终止这个对话。并且UAC应当在请求完全没有应答的时候(客户端transaciton将会通知TU这个超时)客户端transaction终止这个对话。
对于INVITE初始化的对话,终止对话需要发送一个BYE。
12.2.2 UAS行为
在对话中发送的请求,就像其他请求一样,是原子请求。如果UAS收到某个请求,所有的相关状态要么一起改变,要么就一起不变。在某些请求中,请求会影响好几个状态(比如INVITE请求)。
UAS从transaction层收到请求。如果请求的To头域有tag字段,UAS的处理核心需要校验对话的ID,拿请求中的tag和现存的对话相比较。如果匹配成功,那么就是一个在对话中的请求。在这种情况下,UAS首先使用8.2节中的对话外请求处理的步骤。如果请求To头域包括了一个tag字段,但是对话的ID并不匹配现存的对话,UAS可能是因为崩溃而重新启动,或者收到了一个另外(可能是错误的)UAS(UAS可以构造To的tags,这样UAS在灾备恢复下,可以把这个tag看成它自己的)。还有一种简单的可能是请求发送错误了。在这个基础上,UAS可以选择接受或者拒绝请求。在允许的情况下,尽量处理这些请求会提供灾难恢复的机制。UAS如果希望支持这样的特性就必须遵循一些原则,比如用始终使用单调递增的Cseq序列号,甚至是在重新启动之后也这样,在重启动后重建路由集合,处理越界的RTP时间戳和序列号等等。
如果UAS由于不希望重构对话而拒绝这个请求,它必须应答对方一个481(呼叫/事务不存在。Call / Transaction不存在)应答。
对于在对话中接收到的,那些不会用任何形式更改对话状态的请求,比如OPTIONS请求,他们等同于在对话外的处理请求。
如果远端的序列号(remote sequence number)是空的,它必须设置成为请求中的Cseq头域的序列号(sequence number)。如果remote sequence number不是空的,但是请求中的sequence number小于这个remote sequence number,请求就是非顺序的,并且必须通过应答500(服务器内部错误)打回去。如果remote sequence number不是空的,并且请求中的序列号大于这个remote sequence number,请求就是按照顺序的。这个请求中的Cseq的序列号可以比remote sequence number大不止1。在这种情况下,并非是错误的,并且UAS应当准备接收和处理比上次处理的请求Cseq值大于1 的请求。UAS必须设置remote sequence number成为请求中的Cseq头域中的序列号。
如果一个proxy废弃掉一个UAC产生的请求,并且UAC重新递交这个请求的时候。这个请求是会具有一个全新的Cseq序列号。UAS是不会收到第一个请求的,这样,Cseq序列号就会出现间隔,这样的间隔并非是一种错误的情况。
当UAS接收到一个target刷新请求的时候,如果请求中存在Contact头域,它必须用Contact头域中的URI来替换对话的remote target URI。
12.3 终止对话
如果对话外的请求产生了一个非2xx响应的临时应答,任何前边请求创建的”早期对话”(early dialogs)将会终止,该操作时方法无关的。在已经建立的对话中,终结对话就是请求方法相关的。在这个定义中,BYE方法将会终结一个会话及其关联的对话。15节有细致的讨论。