3、SIP协议状态机与事务:客户端与服务端的对话逻辑

聊到SIP协议,很多人第一反应就是那堆复杂的头域和消息。但说实话,真正让SIP跑起来的核心,是它的状态机。我刚开始做SIP协议栈开发时,总觉得状态机这东西太理论,结果第一次写客户端代码就踩了坑——消息发出去后,服务器没回,我的程序直接卡死了。嗯,从那以后,我老老实实把状态机啃了一遍。

今天我们就来聊聊SIP协议的状态机与事务。这部分内容,说白了就是搞清楚「谁在什么时候该干什么」。你想想看,没有状态机,SIP消息就是一盘散沙。

3.1 SIP客户端事务状态机

客户端事务,就是发起请求的那一方。比如你的手机拨出一个INVITE请求,这个请求从发出到收到最终响应,整个过程就是一个客户端事务。

客户端事务的状态机,我习惯把它分成两类:非INVITE客户端事务INVITE客户端事务。为什么分开?因为INVITE请求涉及通话建立,流程更复杂。

3.1.1 非INVITE客户端事务

非INVITE请求,比如REGISTER、OPTIONS、BYE等,它们的处理相对简单。状态机只有三个状态:

  • Trying:请求刚发出,等待响应
  • Proceeding:收到临时响应(1xx),继续等待最终响应
  • Completed:收到最终响应(2xx-6xx),事务结束

这里有个关键点:非INVITE事务的定时器A(重传定时器)和定时器E(最终响应定时器)。我在项目中遇到过一个问题——某个设备发REGISTER请求后,服务器没回任何响应,客户端就傻等着。后来发现是定时器E没设置好,默认超时时间太短,导致频繁重传。

避坑指南:我曾经在调试一个VoIP网关时,发现客户端事务在收到3xx重定向响应后,直接进入了Completed状态,但后续的重定向处理逻辑没跟上。原因是状态机实现中,把3xx当成了最终响应,但业务层还需要继续处理。所以,状态机是状态机,业务逻辑是业务逻辑,别混在一起。

3.1.2 INVITE客户端事务

INVITE事务就复杂多了。它多了一个Calling状态和一个Accepted状态。为什么?因为INVITE请求需要等待对方摘机(200 OK),而且中间可能有多次振铃(180 Ringing)。

INVITE客户端事务的状态流转:

  • Calling:INVITE已发出,等待响应。定时器A控制重传
  • Proceeding:收到1xx临时响应,继续等待最终响应
  • Completed:收到2xx-6xx最终响应,但需要确认(ACK)
  • Accepted:收到2xx响应,且ACK已发出,事务结束

你可能会问:为什么INVITE的Completed状态还要等ACK?因为INVITE的响应可能丢失,服务器需要重传最终响应,直到收到ACK。这就是SIP的可靠性机制。

个人经验:我建议在实现INVITE客户端事务时,特别注意定时器B(INVITE事务超时)。默认值是64*T1(T1通常为500ms),也就是32秒。如果32秒内没收到任何响应,事务直接进入Terminated状态。我曾经见过一个设备,因为网络延迟大,定时器B设置得太小,导致正常通话被提前挂断。

3.2 SIP服务端事务状态机

服务端事务,就是接收请求并返回响应的那一方。比如你的SIP服务器收到一个INVITE,它需要返回100 Trying、180 Ringing,最后是200 OK。

服务端事务同样分两类:非INVITE服务端事务INVITE服务端事务

3.2.1 非INVITE服务端事务

非INVITE服务端事务的状态机相对简单:

  • Trying:收到请求,正在处理
  • Proceeding:已发送临时响应,继续处理
  • Completed:已发送最终响应,等待客户端重传(如果有)

这里有个细节:服务端在Completed状态时,如果收到客户端的重传请求,需要重新发送最终响应。定时器J控制这个状态的持续时间,默认是64*T1。

3.2.2 INVITE服务端事务

INVITE服务端事务是最复杂的。它的状态包括:

  • Proceeding:收到INVITE,已发送1xx响应
  • Completed:已发送2xx-6xx最终响应,等待ACK
  • Confirmed:收到ACK,事务确认

为什么INVITE服务端事务没有Trying状态?因为INVITE请求必须立即返回100 Trying,所以Proceeding状态就是初始状态。

注意:INVITE服务端事务的Completed状态有一个定时器H,控制等待ACK的时间。如果定时器H超时(默认64*T1),服务端会认为ACK丢失,直接进入Terminated状态。我在测试中发现,有些客户端实现不发送ACK,导致服务端一直卡在Completed状态。所以,服务端实现一定要处理好定时器H的超时逻辑。

3.3 SIP对话(Dialog)概念

对话,是SIP协议中一个非常重要的概念。它代表两个用户代理之间的一段持续关系。说白了,一次通话就是一个对话。

对话的核心标识是:Call-IDFrom tagTo tag。这三个字段组合起来,唯一标识一个对话。

对话的建立过程:

  1. 客户端发送INVITE请求,From头域带上自己的tag
  2. 服务端返回200 OK,To头域带上自己的tag
  3. 客户端收到200 OK后,对话建立

对话的生命周期:

  • 建立:收到2xx响应后
  • 维持:通过re-INVITE、UPDATE等请求更新对话状态
  • 终止:收到BYE请求并回复200 OK

我在项目中遇到过一个问题:某个设备在收到BYE后,没有正确清除对话状态,导致后续的INVITE请求被误认为是同一个对话。嗯,这就是对话管理没做好。

3.4 SIP事务与对话的关系

事务和对话,是两个不同层次的概念。事务是消息交换的基本单元,而对话是端到端的逻辑关系。

它们的关系可以用一句话概括:一个对话包含多个事务

举个例子:

  • 第一次INVITE/200/ACK:这是一个事务,同时建立了对话
  • 后续的re-INVITE/200/ACK:这是另一个事务,属于同一个对话
  • 最后的BYE/200:这是第三个事务,用于终止对话

你想想看,如果没有对话的概念,你怎么知道re-INVITE和BYE是属于同一通电话的?对话就是用来关联这些事务的。

核心要点:事务是协议层的概念,对话是应用层的概念。事务保证消息的可靠传输,对话保证端到端的逻辑连续性。我在做模糊测试时,经常利用事务和对话的关系来构造异常场景——比如在对话建立前发送BYE,或者在事务未完成时发送新的INVITE。这些异常情况,往往能挖出协议栈的漏洞。

3.5 知识体系总览

下面这张图,是我自己整理的SIP状态机与对话的关系图。你可以把它当作一个快速参考。

SIP状态机与对话关系图 客户端事务 非INVITE: Trying → Proceeding → Completed INVITE: Calling → Proceeding → Completed → Accepted 定时器A: 重传控制 定时器B: INVITE超时(64*T1) 定时器E: 非INVITE超时 服务端事务 非INVITE: Trying → Proceeding → Completed INVITE: Proceeding → Completed → Confirmed 定时器H: 等待ACK(64*T1) 定时器J: 非INVITE重传等待 定时器I: INVITE重传等待 对话 (Dialog) 标识: Call-ID + From tag + To tag 生命周期: 建立(2xx) → 维持(re-INVITE/UPDATE) → 终止(BYE) 一个对话包含多个事务,事务是对话的组成部分 事务是协议层概念,对话是应用层概念

这张图里,我把客户端事务和服务端事务分开画,下面是对话层。你注意看,事务是短暂的,对话是持久的。一次对话可能包含多个事务,但每个事务只属于一个对话。

我的建议:在做SIP模糊测试时,重点关注事务状态机的边界条件。比如:在Trying状态收到多个相同请求、在Completed状态收到ACK之前收到新的INVITE、对话建立前收到BYE。这些场景,往往是协议栈实现最容易出问题的地方。

好了,关于SIP状态机与对话的内容,就聊到这里。这部分知识是理解SIP协议的基础,也是后续做模糊测试的必备前提。你想想看,连状态机都没搞清楚,怎么去构造异常消息?


公众号:蓝海资料掘金营,微信deep3321