SIP 只干一件事:把「我要和你通话」这句话可靠地送到对方——声音图像它一概不管。 一条报文 628 字节,读完就知道它为什么三十年后还在用。
前八章讲的是 ONVIF:它用 SOAP 走 HTTP 运 XML。 从这一章换一套信令:SIP 走 UDP 运文本,它是 GB/T 28181 的底座。 两者要解决的问题一样(怎么让两台设备谈好一次会话),只是选择的技术路线不同。
一、它管信令,不管媒体
这是理解 SIP 的第一道分界线,也是最多人搞混的地方:
| 它管 | 它不管 |
|---|---|
| 呼叫的建立与拆除 | 音视频数据本身 |
| 对方在不在、忙不忙 | 编码格式怎么压 |
| 媒体往哪发(地址端口) | 丢包了怎么办 |
真正运音视频的是 RTP(第一章讲过), SIP 只负责在通话开始前把「对端地址、端口、编码」这三件事谈好。 所以「SIP 连不上」和「有画面但花屏」是两个完全不同的问题——前者查信令,后者查 RTP。 分不清这一点,排障会一直走错方向。
二、一条报文看全貌
SIP 报文分五段,跟 HTTP 长得几乎一样:
| 段 | 内容 | 关键 |
|---|---|---|
| 起始行 | 方法 + URI + 版本 | 说明要干什么 |
| 头部 | 若干 名字: 值 |
谁发的、发给谁 |
| 空行 | 一个 CRLF | 分隔头与体 |
| 正文 | 通常是 SDP | 媒体怎么谈 |
第二行才是重点:头部里有八个是必需的(Demo 里数过),缺一个对端就不认。
八个必需头分别是:Via、Max-Forwards、From、To、Call-ID、CSeq、Contact、
Content-Length。
它和 HTTP 的关系值得说清:两者都是「文本 + 头 + 空行 + 体」的结构,
HTTP 的方法是 GET/POST,SIP 的方法是 INVITE/BYE。
这不是巧合:SIP 的设计者明确参考了 HTTP 与 SMTP,为的就是「抓包能看懂」。
三、十三个方法,日常只用五个
| 方法 | 干什么 | 用得多不多 |
|---|---|---|
INVITE |
发起会话 | 最常用 |
ACK |
确认收到最终应答 | 最常用 |
BYE |
结束会话 | 最常用 |
CANCEL |
取消还没答的请求 | 常用 |
REGISTER |
向服务器登记地址 | 最常用 |
OPTIONS |
探活、问能力 | 常用 |
MESSAGE |
传短消息 | 国标有用 |
SUBSCRIBE |
订阅事件 | 少 |
前五行加第六行,就是全部日常。
剩下七个(NOTIFY、INFO、PRACK、REFER、PUBLISH、UPDATE、
MESSAGE 之外还有)在监控项目里几乎不出现。
ACK 那个位置要特别注意:它不是「请求」而是「确认」,
所以它没有应答——这一点在 09.01.04 会展开,也是初学者最容易写错的地方。
四、「三次握手」在 SIP 里长什么样
SIP 的会话建立不是三次握手,而是「请求—应答—确认」的扩展版:
| 步 | 谁发 | 内容 |
|---|---|---|
| 1 | UAC | INVITE(我请你通话) |
| 2 | UAS | 100/180/200 |
| 3 | UAC | ACK(我收到了) |
第三行那个 ACK 是 SIP 独有的设计: TCP 有三次握手保证可靠,而 SIP 主要跑 UDP——UDP 没有确认机制,所以要自己在应用层补一个。 补的方法就是 ACK:200 OK 要被反复重传,直到收到 ACK 为止(Timer G/H 管这事)。 这条设计带来一个后果:如果 ACK 丢了,对方会一直重传 200 OK, 而主叫方以为通话已经建立了——两边状态不一致,这就是「幽灵通话」。
五、SIP 到底解决了什么问题
回到「为什么要有个协议」这个问题:
| 没有 SIP 时 | 有 SIP 之后 |
|---|---|
| 每台设备一套私有信令 | 一套通用语言 |
| 换厂家就要改代码 | 换厂家只换个 URI |
| 地址写死在配置里 | 注册 + 查找 |
第三行那个「注册 + 查找」是架构上的关键:设备启动时向服务器登记「我在这个 IP」, 主叫只要知道对方的「号码」,不用知道 IP。 这就是为什么国标平台能用一串 20 位编码找到任意一台摄像机—— 编码是名字,IP 是地址,SIP 负责把名字翻译成地址。 这个思路和 DNS 一样,只不过 DNS 查的是域名到 IP,SIP 查的是号码到 IP。
六、动手 Demo:一条 SIP 请求到底多少字节
Demo 把一条完整的 INVITE 报文打出来,再量几种方法的字节。 报文是按 RFC 3261 的格式拼的,里面的 20 位编码用的是 GB/T 28181 的形状。
msg = S.build_req("INVITE", cseq=1, body=S.sdp())
print(msg)
print("全长", len(msg.encode("utf-8")), "字节")
head, _sep, body = msg.partition("\r\n\r\n")
hl = head.split("\r\n")
print("起始行", len((hl[0] + "\r\n").encode()), "字节")
print("头部", len(("\r\n".join(hl[1:]) + "\r\n").encode()), "字节")
print("SDP ", len(body.encode()), "字节")
实跑输出:
--------------------------------------------------------------------
一条 SIP 请求到底多少字节
--------------------------------------------------------------------
**先把一条真报文完整打出来**,**因为 SIP 的所有秘密都在文本里:**
INVITE sip:34020000001320000001@3402000000 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.100:5060;rport;branch=z9hG4bKbedc329c8d
Max-Forwards: 70
From: <sip:34020000001320000002@3402000000>;tag=3d1f9c2a
To: <sip:34020000001320000001@3402000000>
Call-ID: [email protected]
CSeq: 1 INVITE
Contact: <sip:[email protected]:5060>
Content-Type: application/sdp
Content-Length: 222
v=0
o=34020000001320000002 0 0 IN IP4 192.168.1.100
s=Play
c=IN IP4 192.168.1.100
t=0 0
m=video 10000 RTP/AVP 96 98 97
a=recvonly
a=rtpmap:96 PS/90000
a=rtpmap:98 H264/90000
a=rtpmap:97 MPEG4/90000
y=0100000001
**这一条 628 字节**(含 SDP 正文 222 字节)。
**再把几种常用方法各拼一条,量字节**:
报文 字节 说明
-------------------------------------
OPTIONS 375 无正文
REGISTER 377 无正文
MESSAGE 375 无正文
INVITE 373 无正文
INVITE + SDP 628 带媒体描述
**四个「无正文」的字节数几乎一样**,**因为 SIP 的头部结构固定、长度只差方法名那几个字母。**
**这就是文本协议的特点**:**开销可预测、可肉眼读、但比二进制大一圈。**
**那条 INVITE 按空行切成四段,加起来应该正好是全长**:
段 行数 字节
---------------------------
起始行 1 52
头部 9 352
空 行 1 2
SDP 正文 11 222
**四段相加 628 字节,实测全长 628 字节**——**对得上,说明没漏段也没重复算。**
**必需的头只有八个**:`Via`、`Max-Forwards`、`From`、`To`、`Call-ID`、`CSeq`、`Contact`、
`Content-Length`。
**少任何一个,设备都不认**;**剩下的都是可选**(`Subject`、`Allow`、`Supported` 这些)。
**GB/T 28181 里 `Subject` 是常用的**,
**因为要把媒体参数塞进 INVITE**——**这一条在第九章后面会再遇到。**
输出里前 27 行就是那条报文,它能直接粘进 Wireshark 的「Decode As SIP」里看。
四段相加 628,实测也是 628——这个自洽说明「分段没漏、也没重复算」,
是核对报文结构最笨也最可靠的办法。
注意起始行 52 字节、头部 352 字节:
一条 51 字符的起始行里,光那个 SIP URI 就占 35 字符——
20 位编码是国标的代价,它让每个地址都变长了。
四个「无正文」的方法字节数只差 0~4 字节:375/377/375/373,
差的就是方法名长度(REGISTER 比 INVITE 长两个字母,而它在起始行和 CSeq 里各出现一次,
正好多 4 字节)。
这个「多 4 字节」能对上,说明报文拼接没有多余空格——
手写报文时最容易犯的错就是多一个空格,而设备对起始行的空格数量是严格的。
30 秒实验
- 把
Max-Forwards删掉再发,看 UAS 回什么码。 - 给
Contact加一个不存在的端口,看设备还能不能回。 - 把起始行里的
SIP/2.0改成SIP/3.0,看对端怎么处理。
常见误区
- 以为 SIP 负责传音视频。 它只管信令,媒体走 RTP。 所以「SIP 通了但没画面」是正常现象,要查 RTP 那一层。
- 以为 ACK 也是一个请求、也该有应答。 ACK 是确认不是请求,服务器收到后什么都不回, 等它的应答会一直等到超时。
- 以为 SIP 头可以随便少几个。 八个必需头缺一个就不认, 而且报错往往只是「400 Bad Request」,不说是哪个头缺了。
自测三问(点开对答案)
1. SIP 和 RTP 的分工怎么一句话说清? —— **SIP 谈「在哪发」,RTP 运「发什么」。** 媒体地址、端口、编码由 SDP 谈好, 之后的数据全走 RTP,SIP 不再参与。 2. 为什么 SIP 要有 ACK 这种「没有应答的请求」? —— **因为 SIP 主要跑在 UDP 上,没有传输层确认**。 ACK 就是应用层补的确认,让 200 OK 的重传知道什么时候可以停。 3. 「注册 + 查找」解决了什么工程问题? —— **把「号码」和「IP」解耦**。主叫只要知道 20 位编码,不用知道设备当前 IP; 设备换 IP 只需重新注册。下一节拆开看一条 INVITE 为什么会引出三条应答——那是分层的功劳。
- ☐ (本节完,下一节 09.01.02 SIP的实现机制)