一次完整接入是七步、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 秒实验
- 把两次注册之间的间隔拉长到 2 秒,看总计变成多少。
- 用
time.perf_counter_ns()换成纳秒精度,看抖动有多大。 - 在 UAS 里加一句
time.sleep(0.05),模拟真实设备,看哪步变慢。
常见误区
- 把本机 4 毫秒当成现场指标。 它是「协议固有开销」的下界,现场慢出来的部分是环境与实现。
- 设备离线先查网络。 先查注册续期——
Expires到期没续,平台就显示离线, 而设备还在正常发流。 - 续注册沿用原来的 CSeq。 新事务要加一,沿用会被当重传丢掉,看起来像「注册请求被忽略」。
自测三问(点开对答案)
1. 本机这 4 毫秒的正确用途是什么? —— **当「协议固有开销」的下界基线**。现场比它慢出来的部分,才是网络、设备实现和并发带来的。 2. 「收不到」和「慢」在排查上有什么区别? —— **收不到是丢了(要查丢包与防火墙)**,慢是到了但晚(要查 RTT 与处理)。处理方式完全不同。 3. 设备掉线为什么先查注册? —— **注册有 Expires,到期不续平台就判离线**,而设备可能一直在正常发流。 先查这个能排掉最常见的假离线。下一节讲超时——十个 Timer 里只有四个会真正影响你。
- ☐ (本节完,下一节 09.01.09 SIP超时机制)