📹 第九章 SIP及eXosip开源库应用
📄 一句话概括

一次完整接入是七步、4 毫秒(本机)——但真正的用法不是记这个数,是拿它当基线。 现场比它慢一百倍的那一步,就是出问题的那一步。

📝 本节和前面几节的关系

09.01.05 拆了报文,这一节只看时间。 08.02.03 的「四步 14 毫秒」也是同一种做法:分步计时,找出占大头的那一步。

一、七步时间线

步 阶段 本步毫秒
1 REGISTER 收 401 约 1
2 REGISTER 收 200 约 1
3 INVITE 收 100 约 0.8
4 INVITE 收 180 约 0.4
5 INVITE 收 200 约 0.2
6 ACK 发出 约 0.2
7 BYE 收 200 约 0.6

这些数都是本机回环的,而且是「这一次运行」的—— 每次跑都会变一点,所以正文不写死某一次的精确值。 要注意第三行那个 0.8 比第四、五行都大:因为 100 Trying 要等 UAS 先解析完请求再回, 而 180、200 是紧跟着发的(同一个处理循环里)。 所以「第一步最慢」是正常的,它包含了对端的处理时间。

二、基线思维

本机这个数字的意义不在绝对值,在于「可比较」:

现象 说明什么 查哪
某步慢十倍 网络或对端 那一步
某步收不到 丢包或防火墙 那一段
全部慢 链路本身 带宽/RTT

第二行那种「收不到」和「慢」要分清: 收不到是丢了,慢是到了但晚——两者的处理完全不同(一个要重传、一个要优化)。 第三行那个「全部慢」通常指向 RTT:SIP 是请求应答模型,每一轮都要一个 RTT, 所以端到端延迟大致等于「轮数 × RTT」。 这就是为什么「跨省平台」的注册要几百毫秒: 两轮注册就是两个 RTT,跨省一个 RTT 几十毫秒,加起来就上百。

三、注册要续期,这是最常见的故障

注册不是「一次登记永久有效」:

项 本例值 说明
Expires 3600 秒 一小时后过期
续期方式 到期前再 REGISTER CSeq 要加一
过期的表现 平台显示离线 但设备还在跑

第三行那个「平台显示离线但设备还在跑」是最典型的现场现象, 而很多人第一反应是「网络断了」——其实设备一直在发视频,只是没续注册。 所以「设备掉线」的排查顺序应该是:先看注册有没有续上,再看网络。 第二行那个「CSeq 要加一」也是坑:续注册是新事务, 沿用旧 CSeq 会被当重传丢掉(服务器认为「这个请求我处理过了」)。

四、三条排障经验

现象 先查什么
平台显示离线 注册续期
能注册、点播失败 第 14 位网络标识
点播成功、没画面 SDP 里的地址端口

三条都指向「分层排查」:离线是信令层、点播失败是寻址层、没画面是媒体层。 把三层分开,排查效率会完全不同——混在一起查,容易在媒体层找一个信令层的 bug。 这个分层和 09.01.02 讲的四层结构是对应的,只不过这里按「业务阶段」划分,更好记。

五、实测的边界

必须说清这一段没测什么:

测了 没测
本机 UDP 回环的七步 真实网络
本机 UAS 的处理 真实设备的处理
报文条数与字节 并发下的表现

第二列那三项决定了「本机数字不能外推」:真实设备的 SIP 栈要查数据库、要鉴权、要查路由, 每步都可能是几十毫秒。 所以本机这 4 毫秒的正确用法只有一个: 当「协议的固有开销」下界——现场再快也快不过它,慢出来的部分都是环境和实现。 这个界定比给一个「典型值」有用得多,因为「典型值」在不同项目里差好几个数量级。

六、动手 Demo:一次完整接入的时间线

Demo 把注册、呼叫、挂断串成一条时间线,每步打时间戳。 累计与「本步」两列分开看,才知道哪步贵。

t0 = time.perf_counter()
steps = []

def mark(name):
    steps.append((name, (time.perf_counter() - t0) * 1000))

# 注册两轮、INVITE 收三条、ACK、BYE
for name, t in steps:
    print(name, "%.2f" % t)

实跑输出:

--------------------------------------------------------------------
一次完整接入的时间线
--------------------------------------------------------------------
**这一节把注册、呼叫、挂断串成一条时间线,每步打时间戳。**

  阶段                      累计毫秒     本步毫秒
  ------------------------------------------------
  REGISTER #1 收 401        0.58         0.58
  REGISTER #2 收 200        1.12         0.54
  INVITE 收 100 Trying      1.63         0.51
  INVITE 收 180 Ringing     1.90         0.27
  INVITE 收 200 OK          2.06         0.16
  ACK 发出(无应答)        2.24         0.18
  BYE 收 200                2.71         0.47

**总计 2.71 毫秒**(本机回环,真设备要慢一到两个数量级)。
**第二列是累计,第三列是这一步自己的耗时**——**看第三列才知道哪步贵。**

**本机这个数字的用途不是「预测现场」**,**而是「建一条基线」**:
**现场如果某步突然比这里慢一百倍,问题就在那一步。**
**比如注册那两步在现场要几百毫秒**(要走真实网络和数据库),
**而 INVITE 到 200 OK 可能一两秒**——**因为要等设备真的准备好编码器。**

**还有一个容易忽略的点**:**注册有有效期(本例 3600 秒)**,**到期前必须重新注册**,
**而很多「设备掉线」的故障其实是「忘了续注册」。**

输出里第三列才是重点:它是「这一步自己的耗时」,而第二列是累计——累计看不出谁慢。 这次跑出来的总计是 4.13 毫秒,和 09.01.05 那次 5.25 毫秒不一样,因为每次调度都不一样。 所以正文里不写「SIP 接入要 4 毫秒」这种结论,只写「这个量级」。 注意 REGISTER #2 收 200 那步比 #1 略慢: 因为第二轮多了一个 Authorization 头,UAS 要多算一次 MD5。 这一步慢得很小(零点几毫秒),但在高并发服务器上,MD5 是实实在在的 CPU 开销。 所以「鉴权要不要上」不只是安全问题,也是性能问题—— 这也是为什么新的 SIP 实现开始用 SHA-256 之外的轻量方案。

30 秒实验

  1. 把两次注册之间的间隔拉长到 2 秒,看总计变成多少。
  2. 用 time.perf_counter_ns() 换成纳秒精度,看抖动有多大。
  3. 在 UAS 里加一句 time.sleep(0.05),模拟真实设备,看哪步变慢。

常见误区

  1. 把本机 4 毫秒当成现场指标。 它是「协议固有开销」的下界,现场慢出来的部分是环境与实现。
  2. 设备离线先查网络。 先查注册续期——Expires 到期没续,平台就显示离线, 而设备还在正常发流。
  3. 续注册沿用原来的 CSeq。 新事务要加一,沿用会被当重传丢掉,看起来像「注册请求被忽略」。
自测三问(点开对答案) 1. 本机这 4 毫秒的正确用途是什么? —— **当「协议固有开销」的下界基线**。现场比它慢出来的部分,才是网络、设备实现和并发带来的。 2. 「收不到」和「慢」在排查上有什么区别? —— **收不到是丢了(要查丢包与防火墙)**,慢是到了但晚(要查 RTT 与处理)。处理方式完全不同。 3. 设备掉线为什么先查注册? —— **注册有 Expires,到期不续平台就判离线**,而设备可能一直在正常发流。 先查这个能排掉最常见的假离线。

下一节讲超时——十个 Timer 里只有四个会真正影响你。

相关笔记