第九章 对话
一个对于用户代理关键的概念就是对话。对话表示一个两个用户代理在某些时间进行的点对点的SIP联系。对话确保使用户代理间的消息有序,并正确地路由它们。对话表示一个SIP消息的上下文。RFC3261第8节讨论的UA处理与方法无关的对请求和应答处理是对话外的。这章讨论构造一个对话以及如何在这个对话内发送后继的请求和应答。
对话在每个UA中用一个对话ID标识,这个标识由一个Call-ID值、一个本地标记和一个远程标记组成。每一个UA中的同一个对话的对话ID不是一样的。一个UA中的本地标记与对端UA的远程标记相同。标记是不透明的记号,它确保构造全局唯一的对话ID。
对话ID也与所有包含应答和请求中To头域的标签有关。计算一个消息的对话ID的规则依赖于SIP元素是UAC还是UAS。如果是UAC,对话ID中Call-ID的值被设置到消息的Call-ID中,远程标记被设置为消息的To头域中的标签的值,本地标记被设置为消息中From头域中的标签的值(请求和应答都适用这些规则)。如果是UAS,对话ID中Call-ID的值设置为消息的Call-ID,远程标记的值设置为消息的From头域的标签的值,本地标记被设置为消息的To头域的标签的值。
对话包含某些状态,这些状态用于在对话内将来的消息传递。这些状态由对话ID,一个本地序列号(用于对该UA到对端的请求进行排序),一个远程序列号(用于对对端到该UA的请求进行排序),一个本地URI,一个远程URI,远程目标,一个称为"secure"的布尔标志,和一个URI序列的路由集组成。路由集是发送消息到对端需要被穿越的服务器的列表。对话也能够被置为"early"状态,这种状态由一个临时应答所创建,并且当接收到2xx最终应答后转换成"confirmed"状态。对于对话内的其它的应答来,或者没有应答,"early"状态的对话将被终止。
第一节 创建一个对话
通过对特定方法的请求构造非失败应答来创建对话。在本手册中,只有对INVITE请求进行2xx和101-199的带To标签的应答会创建一个对话。由对一个请求的非最终应答创建的对话处于"early"状态,并且这个对话叫做早期对话。扩展能够为对话定义其它的意义。RFC3261第13节给出了对INVITE方法的详细说明。这里我们描述不依赖于方法的对话创建状态的处理。
UA必须像上面描述的那样为对话ID构成的部分分配相应的值。
UAS行为
当一个UAS应答一个请求,并创建一个对话(像对INVITE的2xx应答),UAS必须从请求中拷贝所有的Record-Route头域的值到应答中(包括URI,URI的参数,和任何Record-Route头域的参数,不管UAS知不知道这些值的意义),并且,UAS必须保持这些值的顺序。UAS必须添加一个cONTACT头域到应答中。Contact头域包含一个UAS希望对话内的后继的请求所使用来联系它的地址(在INVITE方法的情况下,这些后继请求包括对于一个2xx应答的ACK)。通常,这个URI的主机部分是IP地址或者主机的FQDN。这个Contact头域中提供的URI必须是一个SIP或SIPS URI。如果发起对话的请求在Request-URI或者顶层的Record-Route头域值包含SIPS URI,或者如果没有Record-Route头域,顶层的Contact头域包含SIPS URI,那么应答中的Contact头域必须是一个SIPS URI。这个URI应该是全局范围的(也就是说,相同的URI能够在这个对话外的消息中使用)。相同的方法,INVITE的Contact头域中的URI的范围也不被限制在这个对话内。因此它可以UAC用于这个对话外的消息中。
这是UAS构造会话的状态。这个状态必须在整个会话时期被维持。
如果请求使用TLS发送,并且Request-URI包含一个SIPS URI,"secure"标签将被设置为TRUE。
路由集必须被设置成请求的Record-Route头域中的URI列表。要保持列表的顺序并且保持所有URI的参数。如果请求中没有Record-Route头域,路由集必须被设置成空集。这个路由集,即使是空集,也要忽略掉预先存在的路由,它为这个对话中将来的请求所使用。远程目标必须被设置成请求的Contact头域中的URI。
远程序列号必须被设置成请求中CSeq头域中的序列号的值。本地序列号必须是空的。组成对话ID的Call-ID必须被设置成请求中的Call-ID的值。组成对话ID的本地标记被需被设置成对原始请求的应答中To头域的标签(应答的To头域通常包含一个标签),并且组成对话ID的远程标记必须被设置成请求中From头域的标签。UAS必须为接收一个From头域中没有标签的请求做好准备,这种情况下,标签被认为包含一个空值。
这是为了保证向后兼容RFC2543,它要求From头域不能有标签。
远程URI必须被设置成From头域中的URI,并且本地URI必须被设置成To头域中的URI。
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头域,路游戏必须被设置成空集。这个路由集,即使是空集,也要忽略掉预先存在的路由,它为这个对话中将来的请求所使用。远程目标必须被设置成应答的Contact头域中的URI。
本地序列号必须被设置成请求的CSeq头域的序列号的值。远程序列号必须是空(当远程UA发送一个对话内请求时创建它)。对话ID的Call-ID组成部分必须被设置成请求的Call-ID的值。对话ID的本地标记必须被设置成请求的From头域的标签的值,并且对话ID的远程标记必须被设置成应答的To头域的标签的值。UAC必须为接收到一个To头域中没有标签的应答而做准备,这种情况下,认为这个标签的值为空。
这是为了保证向后兼容RFC2543,它要求To头域不能有标签。
远程URI必须被设置成To头域的URI,并且本地URI必须被设置成From头域的URI。
第二节 对话内请求
一但一个对话在两个UA中被创建起来,它们当中的任何一端都需要在对话内初始化新的事务。UA发送请求时将以UAC的角色来使用这个事务。UA接收请求将以UAS的角色。注意,这时UA可能与它们在建立对话的事务中扮演的角色不同。
对话内的请求可能包含Record-Route和Contact头域。尽管如此,即使这些请求可能改变远程目标的URI,但它们不会改变对话的路由集。当一个请求不是目标目标请求时,它不能修改对话的远程目标URI,只有请求是目标刷新请求的时候才能够更改对话远程目标URI。对于被INVITE建立起来的对话来说,唯一的刷新目标的请求是re-INVITE(参看RFC3261第14节)。其它的扩展协议可能会为其它方法建立的会话定义其它的刷新目标的请求。
注意,ACK不是目标刷新请求。
目标刷新请求只能够更新对话的远程目标的URI,而不能更新Record-Route头域指定的路由集。更新路由集将会产生与RFC2543的向后兼容问题。
UAC行为
创建请求
使用许多对话的状态的组成部分来创建一个对话内请求。对话的状态被作为对话的一部分存储在对话中。
请求中To头域的URI必须设置成对话状态中的远程URI。请求中To头域中的标签必须设置成对话ID的远程标记。请求的From头域必须设置成对话状态的本地URI。请求中From头域的标签必须设置成对话ID中的本地标记。如果远程或者本地标记是空,To头域或者From头域的标签必须都被忽略。
对于后继请求的原始请求的To和From头域的URI的使用方法是为了向后兼容RFC2543,RFC2543使用URI作为对话的标识。在本手册中,只有标记被用于对话标识。在本手册的后继修订版本中,这种强制在会话内请求中反映的原始To和From的URI的要求会被取消。
请求的Call-ID必须被设置成对话的Call-ID。对话内请求必须包含严格按照递增并且是临近(每次递增1)的CSeq序列数(当然除ACK和CANCEL,它们的数字等于将被告知的和取消的请求的)。因此,如果本地序列数不是空,那么本地序列数的值必须被递增1,并且这个值必须设置到CSeq头域中。如果本地序列数的值是空,必须按照RFC3261第8.1.1.5节的方针选择一个初始值。在CSeq头域中的方法域必须与请求中的方法域匹配。
之所以使用32位整数,一个客户端在一个单独的呼叫中每秒构造一个请求,需要136年才会被用完。序列数初始值的选择使同一呼叫中的后继请求将不会受限。一个非零的初始值允许客户端使用基于时间的序列数。例如,客户端可以在32位秒钟中选择最高有效的31位作为初始序列数(译者:意思是初始值的选择要小于2的31次方)。
UAC使用远程目标和路由集来构造请求的Request-URI和Route头域。
如果路由集是空的,UAC必须把远程URI写入到Request-URI中。并且UAC必须不向请求中添加Route头域。
如果路由集不是空,并且路由集中第一个URI包含lr参数(RFC3261第19.1.1节),UAC必须将远程URI写入到Request-URI中,并且必须包含一个Route头域,该头域按次序包含路由集的值和所有参数。
如果路由集不是空,并且路由集中第一个URI不包含lr参数,UAC必须将路由集中第一个URI写入到Request-URI中,并且删除掉Request-URI中不允许的所有参数。UAC必须包含Route头域,该头域按次序包含路由集中剩余的所有值和参数。UAC必须将远程URI写入到Route头域的最后一个值。
例如,如果远程目标是 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参数,暗示代理服务器不了解本文档中描述的路由机制,它将作为RFC2543描述的那样用Route头域中第一个值当作待发消息的Request-URI。将Request-URI保存在Route头域的的最后为了保存Request-URI中穿越严格代理的信息(这个信息在遇到宽松的代理服务器时被返回)。
一个UAC应该在任何的对话内目标刷新请求中包含Contact头域,并且除非需要改变,否则URI应该与前面的对话内请求一致。如果secure标志是true,那么这个URI必须是一个SIPS URI。在RFC3261第12.2.2节讨论用于更新远程目标URI的目标刷新请求的Contact头域。这允许UA提供一个新的联系地址,用来在一个对话期间内改变它的地址。
可是,非目标刷新请求不能对对话内的远程URI造成影响。
请求其余部分的组成中RFC3261第8.1.1节描述。
一旦请求被构建,计算服务器地址以及发送请求都使用与对话外请求相同的步骤进行处理(RFC3261第8.1.2节)。
RFC3261第8.1.2节的处理步骤通常使请求发送到由Route头域最顶部的值,如果没有Route头域则由Request-URI标识的地址。在某些限制条件下,允许请求被发送到一个其它指定的地址(例如,在路由集中没有指出的能够对外的代理服务器)。