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 秒实验
- 抓一次完整的播放,看 SETUP 响应里的 Session 和 timeout 各是多少。
- 把保活间隔设成 timeout 的一半,挂一晚上,看日志里有没有 454。
- 故意不发 TEARDOWN 直接断开,立刻重连,看会不会连不上(端口没释放)。
常见误区
- 以为 RTP 在流就说明会话活着。 多数实现只认 RTSP 请求,RTP 不停也照样回收, 表现为「画面在动但操作没反应」。
- 以为 timeout 是客户端定的。 是服务端在 SETUP 响应里给的,客户端只能遵守。
- 以为会话结束只要发 TEARDOWN。 客户端崩溃就没人发,所以服务端必须留超时兜底, 客户端也要在 finally 里发。
自测三问(点开对答案)
1. Session 号是客户端生成还是服务端生成? —— 服务端生成,在 SETUP 的响应里下发。客户端只负责记住并在后续请求里带上。 2. 「60 秒没动静」指的是没有数据还是没有信令? —— 没有信令。RTP 一直在流通常不算活跃,会话照样会被回收。 3. 保活间隔该设多少? —— 取 timeout 的一半,比如 timeout 是 60 就 30 秒一次,这样丢一次还有第二次机会。下一节讲 Transport 头:那对端口为什么必须一奇一偶。
- ☐ (本节完,下一节 01.04.05 RTSP参数)