12. 对话 (Dialogs)
用户代理的一个关键概念是对话 (Dialog)。对话表示两个用户代理之间持续一段时间的点对点SIP关系。对话促进了用户代理之间消息的排序以及两者之间请求的正确路由。对话表示解释SIP消息的上下文。第8节讨论了对话外部请求和响应的方法独立UA处理。本节讨论如何使用这些请求和响应来构造对话, 以及如何在对话内发送后续请求和响应。
对话在每个UA处使用对话ID (Dialog ID) 标识, 对话ID由Call-ID值、本地标签 (Local Tag) 和远程标签 (Remote Tag) 组成。参与对话的每个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 (Dialog ID)
- 本地序列号 (Local Sequence Number) - 用于排序从UA到其对等方的请求
- 远程序列号 (Remote Sequence Number) - 用于排序从其对等方到UA的请求
- 本地URI (Local URI)
- 远程URI (Remote URI)
- 远程目标 (Remote Target)
- "secure"布尔标志 - 指示是否使用安全传输
- 路由集 (Route Set) - URI的有序列表, 是发送请求到对等方需要遍历的服务器列表
对话也可以处于"早期" (Early) 状态, 当它由临时响应创建时发生, 然后在2xx最终响应到达时转换到"已确认" (Confirmed) 状态。对于其他响应, 或者如果该对话根本没有响应到达, 早期对话终止。
12.1 对话的创建 (Creation of a Dialog)
对话通过对具有特定方法的请求生成非失败响应来创建。在本规范中, 只有带有To标签的2xx和101-199响应 (其中请求是INVITE) 将建立对话。由请求的非最终响应建立的对话处于"早期"状态, 称为早期对话 (Early Dialog)。扩展可以定义创建对话的其他方式。第13节提供了特定于INVITE方法的更多详细信息。在这里, 我们描述创建不依赖于方法的对话状态的过程。
UA必须按照下面的描述为对话ID组件分配值。
12.1.1 UAS行为 (UAS Behavior)
当UAS使用建立对话的响应 (例如对INVITE的2xx) 响应请求时, UAS必须将请求中的所有Record-Route头字段值复制到响应中 (包括URI、URI参数以及任何Record-Route头字段参数, 无论UAS是否知道它们) 并且必须保持这些值的顺序。UAS必须向响应添加Contact头字段。Contact头字段包含UAS希望在对话中的后续请求中被联系的地址 (在INVITE的情况下, 这包括对2xx响应的ACK)。通常, 此URI的主机部分是主机的IP地址或FQDN。Contact头字段中提供的URI必须是SIP或SIPS URI。如果发起对话的请求在Request-URI中包含SIPS URI, 或者在最顶部的Record-Route头字段值中包含SIPS URI (如果有), 或者在Contact头字段中包含SIPS URI (如果没有Record-Route头字段), 则响应中的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的值。对话ID的本地标签组件必须设置为对请求的响应中To字段中的标签 (它始终包含标签), 对话ID的远程标签组件必须设置为请求的From字段中的标签。UAS必须准备接收From字段中没有标签的请求, 在这种情况下, 标签被认为具有空值。
这是为了与RFC 2543保持向后兼容性, RFC 2543不强制要求From标签。
远程URI必须设置为From字段中的URI, 本地URI必须设置为To字段中的URI。
12.1.2 UAC行为 (UAC Behavior)
当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的值。对话ID的本地标签组件必须设置为请求的From字段中的标签, 对话ID的远程标签组件必须设置为响应的To字段中的标签。UAC必须准备接收To字段中没有标签的响应, 在这种情况下, 标签被认为具有空值。
这是为了与RFC 2543保持向后兼容性, RFC 2543不强制要求To标签。
远程URI必须设置为To字段中的URI, 本地URI必须设置为From字段中的URI。
12.2 对话内的请求 (Requests within a Dialog)
一旦在两个UA之间建立了对话, 任何一方都可以根据需要在对话内发起新事务。发送请求的UA将承担事务的UAC角色。接收请求的UA将承担UAS角色。请注意, 这些角色可能与UA在建立对话的事务期间所持有的角色不同。
对话内的请求可以包含Record-Route和Contact头字段。但是, 这些请求不会导致对话的路由集被修改, 尽管它们可能修改远程目标URI。具体来说, 不是目标刷新请求 (Target Refresh Request) 的请求不会修改对话的远程目标URI, 而目标刷新请求会修改。对于使用INVITE建立的对话, 定义的唯一目标刷新请求是re-INVITE (参见第14节)。其他扩展可能为以其他方式建立的对话定义不同的目标刷新请求。
请注意, ACK不是目标刷新请求。
目标刷新请求仅更新对话的远程目标URI, 而不更新从Record-Route形成的路由集。更新后者将引入与RFC 2543兼容系统的严重向后兼容性问题。
12.2.1 UAC行为 (UAC Behavior)
12.2.1.1 生成请求 (Generating the Request)
对话内的请求是使用作为对话一部分存储的状态的许多组件构造的。
请求的To字段中的URI必须设置为对话状态中的远程URI。请求的To头字段中的标签必须设置为对话ID的远程标签。请求的From URI必须设置为对话状态中的本地URI。请求的From头字段中的标签必须设置为对话ID的本地标签。如果远程或本地标签的值为null, 则必须分别从To或From头字段中省略标签参数。
在后续请求中使用原始请求的To和From字段中的URI是为了与RFC 2543向后兼容, RFC 2543使用URI进行对话标识。在本规范中, 仅使用标签进行对话标识。预计在本规范的后续修订中将弃用对话中期请求中强制反映原始To和From URI的要求。
请求的Call-ID必须设置为对话的Call-ID。对话内的请求必须在每个方向上包含严格单调递增和连续的CSeq序列号 (每次递增1) (当然, ACK和CANCEL除外, 它们的号码等于被确认或取消的请求)。因此, 如果本地序列号不为空, 则本地序列号的值必须增加1, 并且此值必须放入CSeq头字段中。如果本地序列号为空, 则必须使用第8.1.1.5节的准则选择初始值。CSeq头字段值中的方法字段必须与请求的方法匹配。
对于32位长度, 客户端可以在单个呼叫内每秒生成一个请求约136年, 然后才需要回绕。选择序列号的初始值, 以便同一呼叫内的后续请求不会回绕。非零初始值允许客户端使用基于时间的初始序列号。例如, 客户端可以选择32位秒时钟的31个最高有效位作为初始序列号。
UAC使用远程目标和路由集来构建请求的Request-URI和Route头字段。
如果路由集为空, UAC必须将远程目标URI放入Request-URI。UAC不得向请求添加Route头字段。
如果路由集不为空, 并且路由集中的第一个URI包含lr参数 (参见第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参数, 则指示的代理不理解本文档中描述的路由机制, 并将按照RFC 2543中指定的方式行事, 在转发消息时用它接收到的第一个Route头字段值替换Request-URI。将Request-URI放在Route头字段的末尾会在严格路由器 (Strict Router) 中保留该Request-URI中的信息 (当请求到达松散路由器 (Loose Router) 时, 它将返回到Request-URI)。
UAC应该在对话内的任何目标刷新请求中包含Contact头字段, 除非需要更改它, 否则URI应该与对话内先前请求中使用的相同。如果"secure"标志为true, 则该URI必须是SIPS URI。如第12.2.2节所述, 目标刷新请求中的Contact头字段会更新远程目标URI。这允许UA提供新的联系地址, 如果其地址在对话期间发生变化。
但是, 不是目标刷新请求的请求不会影响对话的远程目标URI。
请求的其余部分按照第8.1.1节中的描述形成。
一旦构造了请求, 就计算服务器的地址并使用与对话外请求相同的程序发送请求 (第8.1.2节)。
第8.1.2节中的程序通常会导致请求被发送到最顶部Route头字段值指示的地址, 或者如果不存在Route头字段, 则发送到Request-URI。在某些限制下, 它们允许将请求发送到替代地址 (例如路由集中未表示的默认出站代理)。
12.2.1.2 处理响应 (Processing the Responses)
UAC将从事务层接收对请求的响应。如果客户端事务返回超时, 则将其视为408 (Request Timeout) 响应。
UAC收到对对话内发送的请求的3xx响应的行为与请求在对话外发送时相同。此行为在第8.1.3.4节中描述。
但是请注意, 当UAC尝试替代位置时, 它仍然使用对话的路由集来构建请求的Route头。
当UAC收到对目标刷新请求的2xx响应时, 它必须用该响应中Contact头字段中的URI (如果存在) 替换对话的远程目标URI。
如果对话内请求的响应是481 (Call/Transaction Does Not Exist) 或408 (Request Timeout), UAC应该终止对话。如果请求根本没有收到响应 (客户端事务会通知TU超时), UAC也应该终止对话。
对于INVITE发起的对话, 终止对话包括发送BYE。
12.2.2 UAS行为 (UAS Behavior)
对话内发送的请求, 与任何其他请求一样, 是原子的。如果UAS接受特定请求, 则执行与其关联的所有状态更改。如果请求被拒绝, 则不执行任何状态更改。
请注意, 某些请求 (例如INVITE) 会影响多个状态片段。
UAS将从事务层接收请求。如果请求在To头字段中有标签, UAS核心计算与请求对应的对话标识符并将其与现有对话进行比较。如果匹配, 这是一个对话中期请求 (Mid-Dialog Request)。在这种情况下, UAS首先应用第8.2节中讨论的对话外请求的相同处理规则。
如果请求在To头字段中有标签, 但对话标识符与任何现有对话都不匹配, UAS可能已经崩溃并重新启动, 或者它可能已经收到针对不同 (可能失败的) UAS的请求 (UAS可以构造To标签, 以便UAS可以识别该标签是针对它正在为其提供恢复的UAS)。另一种可能性是传入请求被简单地错误路由。基于To标签, UAS可以接受或拒绝请求。接受可接受的To标签的请求提供了鲁棒性, 以便对话可以持续存在, 即使在崩溃中也是如此。希望支持此功能的UA必须考虑一些问题, 例如即使在重新启动后也选择单调递增的CSeq序列号、重构路由集以及接受超出范围的RTP时间戳和序列号。
如果UAS希望拒绝请求 (因为它不希望重新创建对话), 它必须使用481 (Call/Transaction Does Not Exist) 状态代码响应请求并将其传递给服务器事务。
不会以任何方式更改对话状态的请求可以在对话内接收 (例如, OPTIONS请求)。它们的处理就像在对话外接收一样。
如果远程序列号为空, 则必须将其设置为请求的CSeq头字段值中的序列号值。如果远程序列号不为空, 但请求的序列号低于远程序列号, 则请求是无序的, 必须以500 (Server Internal Error) 响应拒绝。如果远程序列号不为空, 并且请求的序列号大于远程序列号, 则请求是有序的。CSeq序列号可能比远程序列号高出多于一个。这不是错误条件, UAS应该准备接收和处理CSeq值比先前接收的请求高出多于一个的请求。然后UAS必须将远程序列号设置为请求的CSeq头字段值中的序列号值。
如果代理挑战UAC生成的请求, UAC必须使用凭证重新提交请求。重新提交的请求将具有新的CSeq号。UAS将永远看不到第一个请求, 因此, 它将注意到CSeq号空间中的间隙。这样的间隙不代表任何错误条件。
当UAS接收到目标刷新请求时, 它必须用该请求中Contact头字段中的URI (如果存在) 替换对话的远程目标URI。
12.3 对话的终止 (Termination of a Dialog)
无论方法如何, 如果对话外的请求生成非2xx最终响应, 则通过该请求的临时响应创建的任何早期对话都将终止。终止已确认对话的机制是特定于方法的。在本规范中, BYE方法终止会话和与其关联的对话。有关详细信息, 请参见第15节。
对话状态总结
| 状态组件 | UAC | UAS |
|---|---|---|
| Call-ID | 请求的Call-ID | 请求的Call-ID |
| 本地标签 | From标签 | To标签 (响应) |
| 远程标签 | To标签 (响应) | From标签 |
| 本地URI | From URI | To URI |
| 远程URI | To URI | From URI |
| 本地序列号 | 请求的CSeq | 空 (初始) |
| 远程序列号 | 空 (初始) | 请求的CSeq |
| 远程目标 | 响应的Contact | 请求的Contact |
| 路由集 | Record-Route (反序) | Record-Route (正序) |
| secure标志 | Request-URI是SIPS且使用TLS | Request-URI是SIPS且使用TLS |
本章小结:
第12章定义了SIP中的对话概念, 这是SIP协议的核心机制之一。对话表示两个UA之间的持久关系, 提供了消息排序和正确路由的上下文。对话通过非失败响应创建, 可以处于早期或已确认状态。对话内的请求使用对话状态来构造, 包括路由集、远程目标、序列号等。理解对话机制对于实现SIP会话管理至关重要。