实体就是报文里可选的那块消息体,RTSP 里几乎只有 SDP 会用到它。 Content-Length 数字节不数字符,还得把每行结尾的 CRLF 算进去。
上一节讲清楚了「控制谁」,这一节讲报文里「带什么货」。 它看着是个小概念,但 Content-Length 算错是最常见的对接故障——表现是「服务端一直等实体收全, 客户端以为发完了」。 下一节 01.04.03 讲的容器文件,就是实体里装的那个 SDP。
一、报文的三段结构
RTSP 报文就三段:起始行、头、实体。
| 段 | 例子 | 必须有吗 | |---|---|---|---| | 起始行 | DESCRIBE rtsp://… RTSP/1.0 | 必须有 | | 头 | CSeq: 2 / Accept: application/sdp | 几乎没有例外 | | 空行 | CRLF | 分隔头与实体 | | 实体 | v=0 / o=- … / m=video … | 只有 DESCRIBE 等少数情况 |
那个空行不能省:没有它,解析方不知道头在哪结束,会把实体第一行当成头继续读。 这是手写报文时第一个要检查的地方。
二、七个实体头
实体头只描述实体本身,不描述会话。
| 头 | 作用 | RTSP 里常见吗 | |---|---|---|---| | Content-Type | 实体是什么格式 | 常见,值多为 application/sdp | | Content-Length | 实体多少字节 | 常见,必填 | | Content-Base | 相对 URL 的基准 | 常见 | | Content-Location | 实体的真实地址 | 少见 | | Content-Encoding | 压过没有 | 极少 | | Content-Language | 什么语言 | 极少 | | Expires | 什么时候过期 | 极少 |
日常只用得上前三个。后面四个是从 HTTP 抄过来的,RTSP 场景里基本用不到。
三、Content-Length 的三个坑
坑一:算的是字节不是字符。ASCII 里两者一样,一旦有中文就差三倍。 坑二:忘了每行结尾的 CRLF。五行的 SDP,光换行就十个字节。 坑三:忘了最后一个空行。SDP 以空行结束,那两个字节也要算。
算一遍就清楚了:八行 SDP、全 ASCII,每行末尾 CRLF 两字节、末尾再一个 CRLF, 所以总长 = 所有行字符数之和 + (8+1)×2。
漏算那两个字节的后果:服务端一直等实体收全,客户端觉得发完了,两边僵在那里直到超时。 表现是「请求发出去了,没有任何响应,等三十秒才断」——看到这个现象,先去数 Content-Length。
四、实体和 SDP 的关系
RTSP 的实体里装的基本只有一种东西:SDP。
| 方法 | 实体里装什么 | 谁发 |
|---|---|---|
| DESCRIBE 的响应 | SDP | 服务端 |
| ANNOUNCE 的请求 | SDP | 客户端(推流) |
| SETUP/PLAY/TEARDOWN | 通常没有 | — |
| GET_PARAMETER 的响应 | 可能有参数文本 | 服务端 |
所以看抓包时有个快判法:有实体又有 Content-Type: application/sdp,
那就是 DESCRIBE 或 ANNOUNCE;没有实体的基本都是控制信令。
推流方向(ANNOUNCE)容易忘:它是客户端告诉服务端「我要推什么」,所以实体是客户端写的 SDP, Content-Length 也要客户端算准。
五、动手 Demo:实体就是消息体,长度得自己数准
这段造一份八行的 SDP,把字符数、CRLF 数、总字节数逐项量出来。 最后看「如果漏算末尾那个 CRLF,差多少」。
sdp = [u"v=0", u"o=- 0 0 IN IP4 192.168.1.64", u"s=IP Camera",
u"t=0 0", u"a=control:*", u"m=video 0 RTP/AVP 96",
u"a=rtpmap:96 H264/90000", u"a=control:trackID=0"]
body, nb = _crlf(sdp) # 每行末尾加 CRLF,最后再加一个空行
print(len(sdp), len(body), nb)
# Content-Length 要填 nb,不是 len(body)
实跑输出:
--------------------------------------------------------------------
实体就是消息体,长度得自己数准
--------------------------------------------------------------------
**RTSP 报文分三块:起始行、头、实体。**实体是可选的消息体,
只有 DESCRIBE 的响应一定带——带的是 SDP。
实体头 作用 常见值
-----------------------------------------------------------------
Content-Type 实体是什么格式 application/sdp
Content-Length 实体多少字节 整数,按字节不按字符
Content-Language 用什么语言 zh-CN,很少用
Content-Encoding 压过没有 gzip,很少用
Content-Base 相对 URL 的基准 rtsp://…/101/
Content-Location 实体的真实地址 同 Content-Base
Expires 什么时候过期 日期时间
**造一份最小的 SDP,实测它的字节数:**
行数 8,字符数 136,**字节数 136**。
差在哪儿:每行结尾的 CRLF 是 2 个字节,最后还有一个空行,所以 8 行要 18 个换行字节。
Content-Length 就该填 136。
**填成 134(漏了最后一个 CRLF)是常见的手写错误**,服务端会因为实体不完整一直等。
**中文的坑:**
Content-Length 数的是**字节**。这条 SDP 全是 ASCII,字符数等于字节数;
要是 `s=` 里写了中文,一个汉字 3 个字节,字符数就对不上了。
三个数分别是 8、实体字符数、字节数,中间那个已经含了 CRLF,所以和最后一个数在纯 ASCII 下相等。 差别只会在有中文时出现——那时字符数小于字节数,而 Content-Length 要填字节数。
「差两个字节」这件事值得单独记: SDP 规范要求以空行结束,那个空行就是最后两个字节; 漏了它,长度少 2,接收方会一直等; 多算了也不行——多 2 个字节,接收方会读越界,把后面的东西当实体。
最稳的写法是让代码算,别手填:把 SDP 拼成一个字符串,然后 len(s.encode("utf-8"))。
手填的 Content-Length 是这类对接故障的第一大来源,而且因为「有时对有时错」,特别难查。
30 秒实验
- 把你手上设备的 DESCRIBE 响应抓下来,数一下 SDP 有几行、Content-Length 是多少。
- 按「字符数 + (行数+1)×2」自己算一遍,看和抓到的数对不对得上。
- 把 SDP 里 s= 改成中文,重新算 Content-Length,看字符数和字节数差多少。
常见误区
- 以为 Content-Length 是字符数。 它是字节数。全 ASCII 时两者一样,一有中文就差三倍。
- 以为 SDP 最后那个空行不算长度。 算。它也是两个字节的 CRLF,漏了就会一直等。
- 以为 RTSP 报文都可以带实体。 只有 DESCRIBE/ANNOUNCE 这类才带,PLAY、 SETUP 带实体反而会被当成异常。
自测三问(点开对答案)
1. RTSP 报文的实体里装的最常见的是什么? —— SDP。DESCRIBE 的响应和 ANNOUNCE 的请求都带它,其余方法基本不带实体。 2. 八行全 ASCII 的 SDP,Content-Length 怎么算? —— 所有行字符数之和加上 (8+1)×2。多的那 1 是末尾必须有的空行。 3. Content-Length 少填两个字节,会看到什么现象? —— 服务端一直等实体收全,客户端以为发完了,两边僵住到超时。 表现为「请求发出去了但没有任何响应」。下一节看实体里装的那个 SDP:一行 m= 就是一路媒体,这就是「容器文件」。
- ☐ (本节完,下一节 01.04.03 容器文件)