RTSP 和 HTTP 都是文本协议、都靠请求响应,但它们的分岔点在「记不记得你」和「能不能缓存」。 记住会话的代价是不可缓存,不可缓存就上不了 CDN——这才是直播协议演进的真正主线。
2.8 用抓包把 RTSP 看透了,这一节把它和 HTTP 放在一起比。 后面第 5、6 章的 HLS 和 HTTP-FLV,本质就是「用 HTTP 重做一遍直播」。
一、表面上很像,其实只有四处相同
RTSP 在设计时大量照抄了 HTTP,所以乍看几乎一样:
① 都是纯文本:请求行加头域,一行一条,\r\n 分隔。
② 都是请求—响应:客户端发一条,服务器回一条。
③ 都有状态码:三位数字,200、404、500 这些编号体系直接借用。
④ 都有 URL:rtsp:// 和 http:// 的结构完全相同。
但只有这四处。 再往下走一步就全不一样了——HTTP 是无状态的「取文件」,RTSP 是有状态的「操作一台设备」。 这个区别不是风格差异,是设计目标不同:一个为了「让全世界都能缓存」,一个为了「让一个人能暂停」。
二、分岔点一:记不记得你
HTTP 的每个请求都自带全部信息:我要哪个 URL、我接受什么编码、我带什么 Cookie。 服务器处理完就忘,下一个请求来了重新认。
RTSP 反过来:SETUP 之后服务器给你一个 Session,之后每条请求都要带。 服务器靠它在几百条会话里找到「你这条」。
这个差别带来连锁反应: ① 服务器要存状态 → 内存占用随并发线性增长,十万并发就是一千万级的会话对象。 ② 服务器不能随便重启 → 一重启全部会话作废,所有客户端要重连。HTTP 服务器可以随便重启,因为它是无状态的。 ③ 负载均衡变难 → 同一个会话的请求必须落到同一台机器上,要么做会话粘滞,要么把会话存到共享存储。
第 ③ 条是致命的。 HTTP 只要在 DNS 上轮询就能水平扩容,RTSP 必须保证「同一会话同一台机器」,扩容就没那么自由了。
三、分岔点二:能不能缓存
HTTP 天生可缓存:一个 GET 的结果,谁拿到都一样,所以中间可以插无数层缓存,这就是 CDN。
RTSP 不可缓存,原因有三个: ① 内容是「正在产生的」,不是文件。缓存没法预先取。 ② 每个客户端的状态不同(播到第几秒、暂停还是播),同一份响应不能给第二个人用。 ③ 传输走 UDP 的 RTP,CDN 的缓存节点压根不碰 UDP。
所以 RTSP 的扩展方式是「加服务器 + 做级联」,而 HTTP 的扩展方式是「加缓存节点」。 前者成本高一个数量级:每个观众都要占一份连接和一份带宽,后者一个缓存节点能服务几千人。
这就是为什么 RTSP 在监控领域活得好好的(几十路,自己能扛),却在互联网直播上彻底输给 HTTP 派(百万级观众)。
四、历史是这么走的
把时间线拉出来看,演进逻辑非常清楚:
① 1990 年代中后期,RTSP 出现。 因为它要「控制」——监控要暂停、要回放、要拖时间轴。 HTTP 那时候连「取一段」都做不到,更别说暂停。 ② 2000 年代,互联网直播起来,RTSP 撞墙。 观众一多,服务器就顶不住,而且穿 NAT、穿防火墙都麻烦。 ③ 2009 年,Apple 推出 HLS。 思路是把直播切成一个个几秒的小文件,用普通 HTTP 分发。 代价是延迟涨到十几秒,收益是能直接上 CDN。 这一步的赌注是「观众不在乎多等十秒」。 ④ 2010 年代,HTTP-FLV 出现。 它把延迟压回 1~3 秒,但仍然是 HTTP,仍然能上 CDN。 这是「两个都要」的方案。 ⑤ 2017 年之后,WebRTC 起来了。 它回到 UDP,用丢了不重传换 200 毫秒延迟。 代价是又要面对 NAT 和扩展性问题——绕了一圈,回到了 RTSP 当年的困境。
所以现在没有「最好的协议」,只有「这个场景选哪个」: 监控用 RTSP,大规模直播用 HLS/HTTP-FLV,连麦和互动用 WebRTC。 三者常常同时存在于一个系统里。
五、动手 Demo:RTSP 与 HTTP:一个有记忆,一个每次重来
同样 10 条命令,两种发法:一条连接复用,或者每条都新建连接。 再列一张对比表,看两边真正的差距在哪。
import socket, threading, time
def serve(sock):
while True:
try:
c, _ = sock.accept()
except OSError:
return
buf = b""
while True:
d = c.recv(8192)
if not d:
break
buf += d
while b"\r\n\r\n" in buf:
req, buf = buf.split(b"\r\n\r\n", 1)
cseq = "0"
for l in req.decode().split("\r\n")[1:]:
if l.lower().startswith("cseq:"):
cseq = l.split(":", 1)[1].strip()
c.sendall(("RTSP/1.0 200 OK\r\nCSeq: %s\r\n\r\n" % cseq).encode())
c.close()
srv = socket.socket()
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(("127.0.0.1", 0))
srv.listen(64)
port = srv.getsockname()[1]
threading.Thread(target=serve, args=(srv,), daemon=True).start()
MSG = "OPTIONS rtsp://127.0.0.1/live/test RTSP/1.0\r\nCSeq: %d\r\n\r\n"
N = 10
t0 = time.perf_counter()
s = socket.create_connection(("127.0.0.1", port), 3.0)
for i in range(1, N + 1):
s.sendall((MSG % i).encode())
s.recv(8192)
s.close()
keep_ms = (time.perf_counter() - t0) * 1000
t0 = time.perf_counter()
for i in range(1, N + 1):
s = socket.create_connection(("127.0.0.1", port), 3.0)
s.sendall((MSG % i).encode())
s.recv(8192)
s.close()
new_ms = (time.perf_counter() - t0) * 1000
print("一条连接复用 %.2f 毫秒 单条平均 %.2f 毫秒" % (keep_ms, keep_ms / N))
print("每次新建连接 %.2f 毫秒 单条平均 %.2f 毫秒" % (new_ms, new_ms / N))
print("相对倍数 %.2f" % (new_ms / keep_ms))
实跑输出:
--------------------------------------------------------------------
RTSP 与 HTTP——一个有记忆,一个每次重来
--------------------------------------------------------------------
① 真跑:同样 10 条 RTSP 请求,两种连接方式
方式 总耗时 单条平均 相对
-----------------------------------------------------
一条连接复用 0.86 毫秒 0.09 毫秒 1.00
每次新建连接 6.02 毫秒 0.60 毫秒 6.98
**环回上差别只有 7.0 倍,因为建连几乎不要钱。**
**换成跨省 30 毫秒 RTT,差别就是十几个 RTT 的事。**
② 但真正拉开距离的不是速度,是「记不记得你」
对比项 RTSP HTTP
-------------------------------------------------------------------
状态 服务器记住会话 每个请求独立
播放控制 PLAY/PAUSE/TEARDOWN 没有
传输 命令走 TCP,媒体走 UDP 全走 TCP
缓存 基本不可缓存 层层可缓存
扩到十万观众 很难 天然(加 CDN 就行)
③ 所以历史是这么走的
**RTSP 先出现,因为它要「控制」——监控要暂停、要回放、要拖动。**
**但它不可缓存,观众一多就顶不住。**
**HTTP 派(HLS/HTTP-FLV)后出现,把「控制」扔掉换「可缓存」,于是能上 CDN、能扛千万人。**
**现在的方案通常是两个都上:监控用 RTSP,直播用 HTTP 派。**
环回上差别只有 7 倍左右,因为建连几乎不要钱(都在本机内存里)。 换成跨省 30 毫秒 RTT,每建一次连就多一个 RTT,10 条就是十几个 RTT 的差距。 但真正的差距不在速度——在「记不记得你」。没有状态,就没有暂停、没有回放、没有拖动。
30 秒实验
- 把
N改成100,看两种方式的差距放大到什么程度——建连成本是固定的,命令越多、复用越划算。 - 在服务端加一句
time.sleep(0.01)模拟处理耗时,看倍数怎么变——处理时间长了,建连的占比就被稀释了。 - 把
N条命令改成四种不同的方法(OPTIONS/DESCRIBE/SETUP/PLAY 循环),体会「HTTP 也能这么发,但它不会记住你在播」——这就是状态的价值。
常见误区
- 「RTSP 就是跑在别的端口上的 HTTP」——端口只是表象,无状态 vs 有状态是根本差别。
- 「RTSP 更快所以更好」——环回上确实快几倍,但它在扩展性上彻底输给 HTTP 派。直播选型看的是能扛多少人,不是单条快多少。
- 「HLS 延迟高是技术不行」——那是「可缓存」换来的代价。想低延迟就必须回到 UDP,就回到 RTSP 当年的困境。
自测三问(点开对答案)
1. RTSP 和 HTTP 相同的四点是什么? —— **纯文本、请求响应、三位状态码、URL 结构。**再往下就全不一样。 2. 为什么 RTSP 不能上 CDN? —— **因为有状态、内容正在产生、走 UDP。**缓存节点没法预先取、也没法一份给多人。 3. HLS 的十几秒延迟换来了什么? —— **可缓存。**切片是普通文件,CDN 用现成的静态缓存就能分发。- ☐ (本节完,下一节 2.10 SDP)