HTTP 有流水线,RTSP 明确不推荐——因为请求之间有依赖,状态会乱。 串行六轮往返,只有两路 SETUP 之间没有依赖,可以并发。
这是 01.04 的最后一节,把「怎么发请求」这件事收尾。 前面讲的会话、连接、扩展,最后都要落到「一次发几条」这个操作层面。 下一节 01.05 讲具体的方法,那是这一节的弹药。
一、三种模式
模式这个词说的是「请求和连接怎么配」。
| 模式 | 做法 | RTSP 的建议 |
|---|---|---|
| 持久连接 | 连一次,多条请求串行发 | 默认,就这么用 |
| 非持久连接 | 每条请求重连 | 不推荐,开销大 |
| 流水线 | 不等响应就发下一条 | 不推荐,状态会乱 |
注意第二、三种是两种不同的「不推荐」: 非持久是性能问题——多花握手挥手的时间; 流水线是正确性问题——可能把会话搞乱。 后者严重得多。
二、为什么不能流水线
两个理由,第一个是硬约束,第二个是软约束。
| 理由 | 说明 | 严重程度 |
|---|---|---|
| 请求之间有依赖 | 没 SETUP 完,PLAY 无意义 | 硬:会失败 |
| CSeq 必须单调 | 响应乱序回来配对会乱 | 软:能实现但复杂 |
第一条是根本的:RTSP 的请求不是独立的。SETUP 给某一路分配了端口,PLAY 才有意义; DESCRIBE 拿到 SDP,才知道要 SETUP 几路。 这种依赖链决定了它天然是串行的。
第二条是工程上的:理论上可以给每个请求编号、按号重组响应, 但这样做的好处远小于复杂度——信令本来就没几条,省下的那点往返不值。
三、串行要几轮
一次完整播放的最小轮数,逐条数出来:
| 顺 | 方法 | 要等前面几条 |
|---|---|---|
| 1 | OPTIONS | 0(可以省) |
| 2 | DESCRIBE | 1 |
| 3 | SETUP 视频 | 1 |
| 4 | SETUP 音频 | 1 |
| 5 | PLAY | 1 |
| 6 | TEARDOWN | 0 |
串行下来六轮,公网上每轮 30 毫秒,信令部分就是 180 毫秒。 对「点开就要看」的监控客户端,这个数已经能感觉到了。
能优化的只有第 3、4 条:两路 SETUP 之间没有依赖,可以同时发出去,省一轮。 省完是五轮,150 毫秒——这就是理论下限了。
四、并发 SETUP 的注意事项
两路 SETUP 并发是唯一安全的并行位置,但有两个前提:
| 前提 | 为什么 |
|---|---|
| 两路都已经从 SDP 里拿到 trackID | 不知道 trackID 就没法拼 URL |
| 各自用独立的 CSeq | 否则响应配不上号 |
还有一个坑:并发发出去之后,两条响应的顺序是不确定的。 代码里不能假设「先发的先回」——必须按 CSeq 或按 trackID 认领,而不是按到达顺序。
这个错很常见:写的时候两条请求顺序发,读的时候按顺序读,在局域网里一直没事, 换到有抖动的网络就随机出错。
五、动手 Demo:RTSP 不做流水线,一条一条来
这段把串行六轮的依赖关系列出来,再算公网上的信令耗时。 最后对比「两路 SETUP 并发」能省多少。
steps = ((u"OPTIONS", 0), (u"DESCRIBE", 1), (u"SETUP(视频)", 1),
(u"SETUP(音频)", 1), (u"PLAY", 1), (u"TEARDOWN", 0))
total = 0
for name, dep in steps:
total += 1
T([name, u"要等前面 %d 条" % dep, u"累计 %d 轮" % total], [0, 0, 0])
print(total * 30) # 公网每轮 30 毫秒,串行总耗时
print((total - 1) * 30) # 两路 SETUP 并发后
实跑输出:
--------------------------------------------------------------------
RTSP 不做流水线,一条一条来
--------------------------------------------------------------------
**HTTP/1.1 有流水线(pipelining):不等响应就发下一个请求。RTSP 明确不推荐这么干。**
原因很实在:CSeq 必须单调递增,而响应可能乱序回来,配对逻辑会变复杂;
而且 RTSP 的请求之间有依赖——没 SETUP 完,PLAY 就没意义。
模式 做法 RTSP 的建议
-----------------------------------------------------------
持久连接 连一次,多条请求 默认就这么用
非持久连接 每条请求重连 不推荐,开销大
流水线 不等响应发下一条 不推荐,有状态会乱
**算一笔依赖账:一次完整播放的最小串行步数。**
OPTIONS 要等前面 0 条 累计 1 轮
DESCRIBE 要等前面 1 条 累计 2 轮
SETUP(视频) 要等前面 1 条 累计 3 轮
SETUP(音频) 要等前面 1 条 累计 4 轮
PLAY 要等前面 1 条 累计 5 轮
TEARDOWN 要等前面 0 条 累计 6 轮
**串行下来至少 6 轮往返**(OPTIONS 那步可以省,很多设备不要求)。
走公网每轮 30 毫秒,光信令就是 180 毫秒——对「点开就要看」的监控客户端,这已经能感觉出来了。
所以有的实现会**把两路 SETUP 并发发出去**(它们之间没有依赖),这是唯一能安全并行的位置。
六轮、一百八十毫秒,这是最保守的算法(连 OPTIONS 都算上)。 省掉 OPTIONS 是四轮还是五轮? 数一下:DESCRIBE、两个 SETUP、PLAY,加 TEARDOWN 是五轮, 但如果 TEARDOWN 在退出时发,用户等待的只有前四轮——一百二十毫秒。
两路 SETUP 并发后省一轮,用户等待变成三轮九十毫秒。 再往下就省不动了:DESCRIBE 必须先于 SETUP(不知道 trackID),PLAY 必须最后。
所以「打开一个监控画面要多久」这个指标,信令部分的下限就是「三次往返」—— 剩下的时间全花在等第一个关键帧、等解码器初始化、等渲染器创建上。 优化的时候要分清是哪一段慢,别一上来就怀疑协议。
30 秒实验
- 抓一次播放的完整信令,数一下实际用了几轮往返。
- 把两路 SETUP 改成并发发出去,看总时间少了多少。
- 故意把 PLAY 提到 SETUP 前面,看设备回 455 还是别的东西。
常见误区
- 以为 RTSP 支持 HTTP 那种流水线。 请求之间有依赖,流水线会把状态搞乱——这是正确性问题, 不只是性能问题。
- 以为所有请求都必须严格串行。 两路 SETUP 之间没有依赖,可以并发,这是唯一能安全并行的位置。
- 以为并发发出就按顺序回来。 响应顺序不确定,必须按 CSeq 或 trackID 认领,不能按到达顺序。
自测三问(点开对答案)
1. 为什么 RTSP 不推荐流水线? —— 两个理由:请求之间有依赖(没 SETUP 完 PLAY 无意义),以及 CSeq 必须单调、 响应乱序会打乱配对。前者是硬约束。 2. 一次完整播放最少几轮往返? —— 用户等待部分是三轮:DESCRIBE、两路 SETUP 并发、PLAY。再往下省不动了,因为依赖链固定。 3. 并发发两路 SETUP,读响应时要注意什么? —— 不能假设先发先回。必须按 CSeq 或 trackID 认领响应,否则在有抖动的网络上会随机出错。下一节把 11 个方法逐个过一遍:常用的就五个,其余的知道存在就行。
- ☐ (本节完,下一节 01.05 RTSP重要方法)