RTSP 最要紧的特点是「有状态」——它记得你在播什么、播到哪。 其余特点(文本、控制与数据分离、可扩展)都是从这一点长出来的。
上一节说了 RTSP 是什么,这一节说它「为什么长这样」。 有状态这件事,直接决定了会话管理、心跳保活、连接复用三件事的写法—— 也就是 01.04.04 到 01.04.07 那几节。
一、有状态:这是总开关
HTTP 每个请求都可以当新人对待,RTSP 不行。 因为「正在播」这件事本身就是一段持续的状态:播的是哪一路、游标在哪、是播还是停。
状态存在服务端,客户端靠一个 Session 号去认领它。
这就是为什么 SETUP 之后每条请求都要带 Session 头。
带来三个直接后果: 一、必须保活。状态不能永远留着,超时就被回收(01.04.04)。 二、必须能清理。正常结束发 TEARDOWN,异常结束靠超时兜底。 三、不能用流水线。请求之间有依赖,乱序发会乱套(01.04.09)。
二、文本协议:好调试,也费带宽
RTSP 报文是纯文本,一行一个字段,CRLF 分隔。 好处是抓包一眼能读,坏处是每个字段都要多花几个字节。
| 文本(RTSP) | 二进制(RTP) | |
|---|---|---|
| 可读性 | 直接看 | 要工具解 |
| 开销 | 每字段带名字 | 靠位置,无冗余 |
| 扩展 | 加个头就行 | 要改格式 |
| 频率 | 一场几十条 | 一秒几十上百个 |
这张表说明为什么两个协议一个文本一个二进制:信令少,可读性值钱;媒体多,每个字节都要省。 一条 PLAY 请求一百多字节无所谓,一个 RTP 包多十字节就是 1% 带宽。
三、控制与数据分离
RTSP 跑 TCP,RTP 跑 UDP,各走各的。 这是 RTSP 架构上最重要的一条,也是排障时第一个要分清的东西。
分离带来两个好处理: 信令断了,媒体还能撑一会儿(反过来也成立); 媒体丢包不影响控制——不会因为丢了一个视频包就按不了暂停。
但也有一个坑:两边状态可能不同步。 典型现象是「画面还在动,但暂停没反应」——说明 RTSP 那条 TCP 已经断了,RTP 还在收。 这种时候看日志会发现保活失败,但画面骗过了眼睛。
四、可扩展
RTSP 留了扩展口子,规矩是「要用的先声明」。
| 机制 | 头 | 作用 |
|---|---|---|
| 要扩展 | Require | 我必须用到这个 |
| 表支持 | Supported | 我支持这些 |
| 报不支持 | Unsupported | 这些我都不支持 |
| 私有头 | X- 开头 | 厂商自定义 |
不支持就回 551,不是 501:551 是「动词认识、参数不认」,501 是「动词不认识」。 这个区别在对接时要分清。 详细展开在 01.04.08。
五、缺点也得说清
RTSP 不完美,它的问题在今天更明显:
| 问题 | 后果 | 今天的替代 |
|---|---|---|
| 要开一堆 UDP 端口 | 防火墙难放行 | RTP over TCP |
| 没有 CDN 支持 | 大规模分发难 | HLS/HTTP-FLV |
| URL 不统一 | 对接要逐家适配 | ONVIF |
| 明文认证 | 密码易泄露 | rtsps/ONVIF 鉴权 |
但它没有消失,因为监控这个场景正好不吃这些亏:局域网不怕端口、路数固定不需要 CDN、 有 ONVIF 兜住 URL、内网可以忍明文。 技术在不在,取决于场景用不用得上——不是新的就一定赢。
六、动手 Demo:RTSP 是有状态的,状态就记在这几个数里
这段把「一个会话要记住什么」逐项列出来,再算总账。 每一项后面都写了「记不住会怎样」——这些就是真实排障时的报错。
# 一个 RTSP 会话在服务端要维持的状态
rows = ((u"Session ID", 8, u"下次请求对不上,回 454"),
(u"状态机", 4, u"PLAY 之前没 SETUP,回 455"),
(u"RTP/RTCP 端口对", 4, u"数据发到没人听的端口"),
(u"当前游标", 4, u"暂停再播从头开始"))
tot = sum(r[1] for r in rows)
print(tot) # 单个会话的字节数
print(tot * 100) # 一百路的总量
实跑输出:
--------------------------------------------------------------------
RTSP 是有状态的,状态就记在这几个数里
--------------------------------------------------------------------
**HTTP 每来一个请求都可以当新人对待,RTSP 不行。**
因为播放这件事本身就是一段持续的状态:已经 SETUP 了哪个流、游标在哪、是播还是停。
特征 HTTP RTSP
----------------------------------------------------------------------
连接 短连接为主 长连接,整场播放不断
状态 无状态 有状态,靠 Session 头维系
默认端口 80/443 554/322
报文格式 文本,CRLF 分隔 同 HTTP 的文本格式
方法数 GET/POST/PUT 等 8 个 11 个,见 D14
带实体 经常 只有 DESCRIBE 的响应带 SDP
**一个会话要记住的东西,可以逐项点出来:**
要记的 大小 记不住会怎样
-------------------------------------------------------------
Session ID 8 字节 下次请求对不上,回 454
状态机 4 字节 PLAY 之前没 SETUP,回 455
RTP/RTCP 端口对 4 字节 数据发到没人听的端口
当前游标 4 字节 暂停再播从头开始
传输方式 1 字节 该发 UDP 的发成 TCP
**一台 IPC 只接 1 路是 21 字节,接 100 路就是 2100 字节。**
数字很小,但**每个会话都得有个独立的状态机**——这才是 RTSP 服务端吃内存的地方,不是这几十字节。
总账只有二十字节,一百路也才两千字节——这个数小得惊人。 但它不是重点:真正吃资源的是「每一路都要有一个独立的状态机实例」, 以及每一路都要占一对 UDP 端口、一个定时器。
端口才是硬约束:一路要一对(RTP+RTCP),一千路就是两千个端口。 很多设备标称「支持 64 路」不是 CPU 不行,是端口和句柄到顶了。
那张表的第三列是本节最实用的部分,把这几个状态码记住: 454 是 Session 不存在(超时被回收了,重新 SETUP); 455 是方法在当前状态下不合法(没 SETUP 就 PLAY)。 看到 454 就去查心跳,看到 455 就去查调用顺序——这两个错占了对接问题的一大半。
30 秒实验
- 用播放器打开一路 RTSP,抓包看 SETUP 之后有多少条请求带了 Session 头。
- 把播放器挂一晚上,第二天看日志里有没有 454——有就说明心跳没做好。
- 故意先发 PLAY 再发 SETUP,看服务端回 455 还是 454,记下这台设备的习惯。
常见误区
- 以为 RTSP 和 HTTP 一样是无状态的。 它靠 Session 维系一段会话,所以必须保活、必须清理、 不能流水线。
- 以为信令断了画面就一定断。 两条通道独立,RTSP 断了 RTP 还能撑, 表现为「画面在动但操作没反应」。
- 以为有状态就是「服务端记住一切」。 服务端只记会话级状态, 客户端也要记(比如自己 SETUP 了哪几路),两边不同步就出怪问题。
自测三问(点开对答案)
1. 为什么 RTSP 不能用流水线? —— 请求之间有依赖——没 SETUP 完,PLAY 没有意义。而且 CSeq 必须单调,响应乱序回来配对会乱。 2. 为什么信令用文本、媒体用二进制? —— 信令一场只有几十条,可读性值钱;媒体一秒上百个包,每个字节都要省。 频率差两个数量级,选择就相反。 3. 看到设备回 454,该查什么? —— 查会话是否已被超时回收——也就是心跳有没有按时发。重新 SETUP 一次就能恢复。下一节把五步流程走一遍:从 OPTIONS 到 TEARDOWN,每条报文长什么样。
- ☐ (本节完,下一节 01.03 RTSP交互流程)