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

八条报文跑完一次完整接入:两轮注册、一轮呼叫、一轮挂断—— 本机 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 秒实验

  1. 把第二轮注册的 response 故意改错一个字符,看服务器回什么码。
  2. 把 INVITE 的 SDP 换成一个空正文,看 UAS 还回不回 200。
  3. 在 ACK 之前插一条别的请求,看服务器会不会算它成新事务。

常见误区

  1. 以为注册一轮就能成。 SIP 的 nonce 由服务器生成,客户端算不出,所以必须两轮。 ONVIF 那边反过来,一轮就行。
  2. 把 SIP 的鉴权和 ONVIF 的鉴权当成一回事。 一个是 MD5、一个是 SHA1; 一个服务器出 nonce、一个客户端自己生成。
  3. 以为通话期间 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 比,到底差在哪。

相关笔记