八条报文跑完一次完整接入:两轮注册、一轮呼叫、一轮挂断—— 本机 5 毫秒上下,真设备慢一到两个数量级。 两轮注册不是多余,是「服务器出题、客户端答题」的必然结果。
09.01.04 讲了三元组怎么标识对话,这一节把对话真正跑起来。 08.02.03 的「设备搜索四步」是 ONVIF 的入口流程, 这一节是 SIP 的对应物,只是它多了一步注册。
一、八条报文的顺序
| # | 报文 | 作用 |
|---|---|---|
| 1~2 | REGISTER 两轮 | 登记地址 |
| 3 | INVITE |
发起呼叫 |
| 4~6 | 100/180/200 |
停重传、振铃、接通 |
| 7 | ACK |
确认(无应答) |
| 8 | BYE → 200 |
挂断 |
第 1~2 行占了两条报文,而它们干的是同一件事——这是 SIP 鉴权设计造成的,下面单独讲。 第 7 行那个 ACK 之后紧跟着就是通话(媒体走 RTP), SIP 在这段时间里一条报文都不发——有人以为「SIP 一直在跑」,其实它在通话期间是静默的。 所以「通话中 SIP 断了」不影响画面,只影响「挂断」这一步(BYE 发不出去)。 这一点和 RTSP 不同:RTSP 断了要保活,SIP 不用。
二、两轮注册:服务器出题、客户端答题
为什么不能一轮搞定? 因为客户端算不出答案:
| 轮次 | 带什么 | 服务器回什么 |
|---|---|---|
| 第一轮 | 不带鉴权 | 401 + nonce |
| 第二轮 | 带 response | 200 OK |
第一轮那个 401 里带了 WWW-Authenticate,里面有 realm 和 nonce 两个关键值。
nonce 是服务器临时生成的——客户端必须拿它参与摘要计算,
所以「不知道 nonce 就算不出 response」,一轮注册在设计上就不可能。
这和 ONVIF 的 WS-Security 不一样:那边是客户端自己生成 nonce(08.04 讲过),
所以 ONVIF 一次就能带上鉴权。
两者的差别在于「谁掌握随机数」:掌握随机数的一方掌握防重放的能力。
三、SIP 的摘要算法是 MD5
这一点必须和第八章分清,因为两章都叫「鉴权」:
| 项 | ONVIF(第八章) | SIP(本章) | |---|---|---|---| | 算法规格 | WS-Security | RFC 2617 | | 哈希 | SHA1 | MD5 | | nonce 谁生成 | 客户端 | 服务器 | | 编码 | Base64 | 十六进制 |
第二行那个 MD5 是 SIP 的现状:RFC 2617 定的是 MD5, 虽然早就不该用于新系统,但存量设备全按它实现——所以做国标对接时,MD5 是绕不过去的。 第三行的差别带来一个实际后果:SIP 必须先被拒一次才能鉴权,而 ONVIF 可以第一次就带上。 所以 SIP 注册永远是两条报文起步,在高延迟网络上这一来一回很贵。
四、ACK 为什么没有应答
这是 SIP 里最反直觉的一条:
| 问题 | 答案 |
|---|---|
| ACK 是请求吗 | 是,但不是「要办什么事」的请求 |
| 它要应答吗 | 不要,它就是应答的应答 |
| 丢了怎么办 | 服务器重传 200 OK |
第三行是关键: 可靠性不是靠「ACK 有应答」实现的,而是靠「200 OK 一直重传直到收到 ACK」实现的。 所以如果 ACK 丢了,服务器会按 T1 翻倍的节奏重发 200 OK(Timer G), 主叫再回一次 ACK 就行。 这个设计很巧:它把「确认」的责任放在接收方(重传)而不是发送方(等待), 于是 ACK 可以完全不等结果。 代价是「幽灵通话」:ACK 一直丢,服务器一直重传,主叫以为通了, 而实际上服务器的状态机还停在「等 ACK」。
五、挂断为什么是两条
BYE 是普通请求,所以它有应答:
| 报文 | 方向 | 含义 |
|---|---|---|
BYE |
任一方都可以发 | 我要挂了 |
200 OK |
对方 | 同意,对话结束 |
第一行那个「任一方都可以」值得注意:SIP 没有「主叫才能挂」的规矩, 被叫先挂也一样发 BYE。 这和电话网的「谁先挂谁计费」不一样——SIP 只管会话,不管计费。 第二行那个 200 之后对话就作废了(09.01.04 讲过),所以「挂断失败」的后果是「对话还活着」, 而不是「电话没挂掉」。 现场遇到「平台显示在线但一直占线」, 多半就是 BYE 丢了、对话没清掉——这时候要等超时,或者由平台强制清。
六、动手 Demo:注册到挂断,全流程八条报文
Demo 真跑完整流程:两轮注册、一次呼叫、一次挂断,每步单独计时。 UAS 是本地起的,端口每次不同。
uas = S.UAS()
port = uas.start()
r1 = S.build_req("REGISTER", cseq=1, req_uri="sip:3402000000")
a1, _ = uas.req_exchange(r1) # 401 + nonce
rsp = S.digest(S.sid(2), uas.realm, uas.pwd, "REGISTER",
"sip:3402000000", uas.nonce)
r2 = S.build_req("REGISTER", cseq=2, req_uri="sip:3402000000",
extra=[("Authorization", "Digest ... response=\"%s\"" % rsp)])
a2, _ = uas.req_exchange(r2) # 200 OK
inv = S.build_req("INVITE", cseq=1, body=S.sdp())
# 发 INVITE,收 100 / 180 / 200,发 ACK,发 BYE
实跑输出:
--------------------------------------------------------------------
注册到挂断,全流程八条报文
--------------------------------------------------------------------
**这一节要跑完整流程**:注册两轮、呼叫一轮、挂断一轮。
UAS 起在 127.0.0.1:57464
# 方向 报文 字节 含义 毫秒
---------------------------------------------------------------------------
1 UAC → UAS REGISTER 356 401 要鉴权 0.92
2 UAC → UAS REGISTER 539 200 注册成功 1.20
3 UAC → UAS INVITE 628 (已发出) 0.36
4 UAS → UAC SIP/2.0 100 Trying 280 停重传 0.28
5 UAS → UAC SIP/2.0 180 Ringing 281 振铃 0.24
6 UAS → UAC SIP/2.0 200 OK 584 接通,带 SDP 0.23
7 UAC → UAS ACK 367 **无应答** 0.20
8 UAS → UAC SIP/2.0 200 OK 273 挂断完成 0.60
**八条报文,4.05 毫秒跑完**,**总字节 3308**。
**注意第 3 行那个 INVITE 是 628 字节**,**比两轮注册报文都大**,**大的那部分是 SDP 正文。**
**两轮注册是规矩不是多余**:
**第一轮故意不带鉴权,服务器回 401 给 nonce**;**第二轮带上算好的 response,才回 200。**
**这个顺序不能倒过来**——**nonce 是服务器出的,客户端猜不到。**
**所以「注册失败」的第一步排查永远是**:**看第一轮 401 有没有收到。**
**第 7 行那个 ACK 没有应答**,
**因为 ACK 本身就不需要应答**——**它是「确认收到了」的意思,不是请求。**
**如果这里等了应答,程序会卡在超时**,**而错误信息只会说「收不到响应」。**
表里八行、总字节 3308,是这一次真跑的结果——每次运行字节数完全一样(因为标识都固定),
只有端口和毫秒会变。
「字节固定、耗时变化」这个特性很有用:
它能区分「报文拼错了」和「网络慢」两种情况——字节数不对说明拼错,字节对但慢说明网络。
第 2 行那个 539 字节比第 1 行多 183 字节,
多的就是 Authorization 头——
摘要鉴权在报文里的体积代价大约 180 字节,这个数字在评估带宽时用得上。
第 5 行那个 180 Ringing 只比 100 Trying 多 1 字节,因为它们结构一样,
只差状态码和原因短语的长度(Trying 与 Ringing 差 1 个字母)。
最后一行 273 字节是最小的 200 OK:没有 SDP、没有 Contact,就是「知道了」这么一句。
30 秒实验
- 把第二轮注册的 response 故意改错一个字符,看服务器回什么码。
- 把 INVITE 的 SDP 换成一个空正文,看 UAS 还回不回 200。
- 在 ACK 之前插一条别的请求,看服务器会不会算它成新事务。
常见误区
- 以为注册一轮就能成。 SIP 的 nonce 由服务器生成,客户端算不出,所以必须两轮。 ONVIF 那边反过来,一轮就行。
- 把 SIP 的鉴权和 ONVIF 的鉴权当成一回事。 一个是 MD5、一个是 SHA1; 一个服务器出 nonce、一个客户端自己生成。
- 以为通话期间 SIP 一直在跑。 通话期间 SIP 是静默的,断了不影响画面,只影响挂断。
自测三问(点开对答案)
1. 为什么 SIP 注册必须两轮? —— **nonce 由服务器生成**,客户端必须先拿到它才能算摘要。 这与 ONVIF 相反——那边客户端自己造 nonce,所以一轮就够。 2. SIP 与 ONVIF 的鉴权在算法上差在哪? —— **SIP 用 RFC 2617 的 MD5、ONVIF 用 WS-Security 的 SHA1**; 编码一个是十六进制、一个是 Base64。 3. ACK 丢了会怎样? —— **服务器按 T1 翻倍重传 200 OK**(Timer G),主叫再回一次 ACK 即可。 但一直丢会造成「幽灵通话」——服务器还在等 ACK。下一节换个参照系:H.323 与 SIP 比,到底差在哪。
- ☐ (本节完,下一节 09.01.06 H.323协议和SIP的比较)