📹 第一章 流媒体与RTSP/RTP/RTCP简介
📄 一句话概括

Session 号是 RTSP 有状态的凭证,SETUP 成功后由服务端发给你。 60 秒没动静就被回收,纯播放不保活就会「画面在动但操作全废」。

📝 本节和前面几节的关系

上一节讲了多路媒体,这一节讲「这几路媒体挂在谁名下」。 会话是 RTSP 区别于 HTTP 的核心机制, 也是监控对接里报错最多的一环——454 这个状态码几乎全来自这里。

一、Session 头长什么样

SETUP 的响应里会出现它,格式很简单:

Session: 1A2B3C4D;timeout=60

前半是会话 ID,后半是超时秒数。

成分 含义 可变吗
ID 服务端生成的唯一标识 由服务端定,客户端不能用
timeout 多久没请求就回收 服务端建议,客户端应遵守

timeout 可以省略,省略时按协议默认 60 秒。有的设备给 30,有的给 300,别假设。

客户端要做的就一件:把 ID 记下来,之后每条请求都带上。 不带就是 454——服务端不知道你说的是哪个会话。

二、超时是怎么算的

「60 秒没动静」指的不是「60 秒没数据」,是「60 秒没信令」。 这是最容易搞错的一点:RTP 一直在来,服务端也可能照样回收会话。

服务端怎么判 算不算活跃 后果
收到任意 RTSP 请求 算 刷新计时
收到 RTCP 包 看实现 有的算,有的不算
RTP 一直在流 通常不算 照样回收

所以「一直播」并不等于「会话活着」——这可能反直觉,但确实是多数实现的真实行为。

第三种情况造成的现象特别迷惑:画面还在动(RTP 没停), 但一按暂停就没反应(RTSP 会话已经没了)。 用户会说「卡住了」,其实是会话悄悄过期。

三、保活该多久一次

规矩是:保活间隔必须小于 timeout。工程上取一半,留出重试余地。

timeout 保活间隔 理由
60 秒 30 秒 一半,丢一次还有第二次机会
30 秒 15 秒 同上
300 秒 150 秒 少见,但按同比例

保活用哪个方法,各家不一样: OPTIONS 最通用,但有些设备嫌它开销大; GET_PARAMETER 是最常见的做法,不带参数时就是纯心跳; SET_PARAMETER 也有的设备认。

最稳的做法是看 OPTIONS 响应里的 Public 头:里面列了设备支持哪些方法,从里面挑一个当保活。 如果只支持 OPTIONS,那就只能用它。

四、会话怎么结束

两种结束方式,缺一不可:

方式 谁触发 可靠性
TEARDOWN 客户端主动 可靠,立刻释放
超时回收 服务端兜底 慢,但要留着

只靠 TEARDOWN 不行:客户端崩了、网线拔了,就没人发它。 所以服务端必须有超时兜底,否则端口会被永久占住。 只靠超时也不行:正常情况下干等六十秒才释放,一千路就是一千对端口白占一分钟。

客户端这边也有个坑:程序退出时忘了发 TEARDOWN,下次启动再连,设备那边的端口还没释放, 可能直接连不上。 所以退出路径要 finally 里发 TEARDOWN,这和多线程里 finally 送哨兵是同一个道理。

五、动手 Demo:会话靠一个 ID 活着,超时前得续

这段生成一个 32 位随机会话号,算算它够不够用,再算出不同时长的直播要发多少次心跳。 顺带看看撞号概率——这个数常被高估。

random.seed(35)
sid = u"%08X" % random.getrandbits(32)
print(len(sid), len(sid) * 4, len(sid) // 2)   # 8 字符、32 位、4 字节
# 1000 路同时在线,撞号概率
print(2 ** 32 // 1000)

# 保活次数:间隔 30 秒
for hours in (1, 8, 24):
    print(hours, hours * 3600 // 30)

实跑输出:

--------------------------------------------------------------------
会话靠一个 ID 活着,超时前得续
--------------------------------------------------------------------
**SETUP 成功时,服务端回一个 Session 头,后面每条请求都要带上它。**
  格式是 `Session: <ID>;timeout=<秒>`。

  真实的样子:Session: 8C773FE6;timeout=60

  ID 是 8 个十六进制字符,也就是 32 位、4 字节的随机量。
  32 位看着不多,但同时在线 1000 路,撞号概率是 4294967 分之一——
  够用了,**所以大多数实现偷懒用 32 位,而不是 RFC 建议的 64 位**。

**timeout=60 意味着:60 秒内没有任何请求,会话就被回收。**
  纯播放(一直不收发信令)怎么办?两条路:

  办法            做法                                        代价
  ----------------------------------------------------------------------------
  定时保活        每 30 秒发一次 OPTIONS 或 GET_PARAMETER     一路一条心跳
  靠 RTP 续命     有的服务端收到 RTCP 也算活跃                不通用

  保活间隔 30 秒,播  1 小时要发 120 次心跳。
  保活间隔 30 秒,播  8 小时要发 960 次心跳。
  保活间隔 30 秒,播 24 小时要发 2880 次心跳。

  **这一条是国标对接里最容易被忽略的**:一路 24 小时播,心跳断了服务端就悄悄把会话回收,
  画面看着还在动(RTP 还在发),但一拖进度条就失败。

32 位、4 字节、8 个十六进制字符——这就是一个会话号的全部。 撞号概率的分母是四十二亿,一千路同时在线摊下来还有四百万分之一,够用了。 所以多数实现偷懒用 32 位,尽管 RFC 建议 64 位。这不是标准妥协,是概率够低。

心跳次数那三个数才是运维要看的:24 小时播一路要发 2880 次心跳。 一千路就是两百八十八万条请求——这还是「只保活」的量,不算任何业务操作。 这就是为什么大平台要做「一个连接多路复用」, 也是为什么很多国标平台干脆用 SIP 的注册机制替代 RTSP 保活。

回看正文那个坑:RTP 一直在流不算活跃,所以「画面还在动」绝不能当成「会话还活着」的证据。 判断会话活没活,唯一可靠的办法是发一条请求看回不回。

30 秒实验

  1. 抓一次完整的播放,看 SETUP 响应里的 Session 和 timeout 各是多少。
  2. 把保活间隔设成 timeout 的一半,挂一晚上,看日志里有没有 454。
  3. 故意不发 TEARDOWN 直接断开,立刻重连,看会不会连不上(端口没释放)。

常见误区

  1. 以为 RTP 在流就说明会话活着。 多数实现只认 RTSP 请求,RTP 不停也照样回收, 表现为「画面在动但操作没反应」。
  2. 以为 timeout 是客户端定的。 是服务端在 SETUP 响应里给的,客户端只能遵守。
  3. 以为会话结束只要发 TEARDOWN。 客户端崩溃就没人发,所以服务端必须留超时兜底, 客户端也要在 finally 里发。
自测三问(点开对答案) 1. Session 号是客户端生成还是服务端生成? —— 服务端生成,在 SETUP 的响应里下发。客户端只负责记住并在后续请求里带上。 2. 「60 秒没动静」指的是没有数据还是没有信令? —— 没有信令。RTP 一直在流通常不算活跃,会话照样会被回收。 3. 保活间隔该设多少? —— 取 timeout 的一半,比如 timeout 是 60 就 30 秒一次,这样丢一次还有第二次机会。

下一节讲 Transport 头:那对端口为什么必须一奇一偶。

相关笔记