🎬 第二章 RTSP流媒体协议
📄 一句话概括

应答比请求多一个东西:状态行里那个三位数状态码。 两百多个 HTTP 状态码在这里只剩八个,其中 454 是 RTSP 独有的。

📝 本节和前面几节的关系

2.2.1 拆了请求,这一节拆应答。两边结构几乎对称,唯一的不对称是「状态码」和「可能带体」。 2.8 抓包分析时,一眼扫过去认的就是这一行。

一、状态行 + 头域

RTSP/1.0 200 OK——这第一行是状态行,三部分:

① 版本:RTSP/1.0,和请求一致。 ② 状态码:三位数字,200、454 这种。代码里判断就靠它。 ③ 原因短语:OK、Session Not Found。给人看的,程序不该依赖它。

后面跟头域,同样以空行结束。 大多数应答没有体,所以空行之后就到头了。 但 DESCRIBE 的应答例外——它后面跟着一整份 SDP。

判断有没有体,只看 Content-Length。 有它就照着长度读那么多字节,没有就当体为空。 这一条和 HTTP 完全一样,但 RTSP 里出错的人更多——因为很多人以为「头域读完了就完了」,结果把 SDP 当成下一条响应。

二、八个状态码

RTSP 的状态码借了 HTTP 的编号体系,但只用了很小一部分:

① 200 OK:一切正常。占实际流量的九成以上。 ② 400 Bad Request:报文格式错、CSeq 对不上、缺必填头域。 ③ 401 Unauthorized:要认证。监控摄像头基本都开着。 ④ 404 Not Found:这个 URL 没有对应的流。 ⑤ 405 Method Not Allowed:服务器不支持这个方法。 ⑥ 454 Session Not Found:RTSP 独有,Session 号不认识或已过期。 ⑦ 461 Unsupported Transport:Transport 头里的协议不支持。 ⑧ 500 Internal Server Error:服务器自己出问题了。

排查时记住这个顺序:先看 4xx 还是 5xx。 4xx 是「你发的东西有问题」,5xx 是「服务器自己的问题」。 RTSP 里 4xx 占绝大多数,而 4xx 里又以 454 最多。

三、454 和 461:两个只在 RTSP 见到

这两个码值得单独说,因为它们是 RTSP 特有的失败模式。

454 Session Not Found 含义是「你说的这个会话我不认识」。三种原因: ① 忘了带 Session 头——最常见,自己写的客户端十有八九栽在这。 ② Session 号抄错了——比如把 ;timeout=60 一起抄进去了。 ③ 会话已经超时被服务器回收——停了超过 60 秒。

461 Unsupported Transport 含义是「你要的传输方式我不会」。 典型场景:客户端要 RTP/AVP/TCP,服务器只支持 UDP;或者反过来,穿防火墙时非要 TCP,老设备不给。

遇到 461 的正确做法不是改代码,是改 Transport 头重试。 先试 UDP,失败了再降级到 TCP——这是工业客户端的标准做法。

四、原因短语不能当判据

这一条是硬规矩:判断成功失败,只看状态码数字,不要匹配文本。

原因是各家的原因短语五花八门。 同样是 200,有的设备写 OK,有的写 OK(带尾空格),有的甚至写成设备自己的话。 同样是 454,标准是 Session Not Found,但某些国产摄像头会写成 Session Not Found 或 Session not found。

反过来说,状态码本身也可能不合规。 有些廉价设备在会话过期时回 400 而不是 454,还有的回 200 但后面根本不发数据。

所以工业级客户端通常这么写: ① 正常路径:状态码 200 且关键头域齐全(比如 SETUP 要有 Session),才算成功。 ② 异常路径:4xx 一律当失败,按码分类处理;超时(收不到任何响应)也算失败——这条最容易被忘。

五、动手 Demo:正常应答与失败应答,一次看全

同一条服务器,先正常发 OPTIONS 拿 200,再故意发一个不存在的方法拿 405。 然后把八个状态码列成表,标出哪些是 RTSP 独有的。

import socket, threading

CODE = {200: "OK", 400: "Bad Request", 401: "Unauthorized",
        404: "Not Found", 405: "Method Not Allowed",
        454: "Session Not Found", 461: "Unsupported Transport",
        500: "Internal Server Error"}

def resp(code, cseq, extra=""):
    return "RTSP/1.0 %d %s\r\nCSeq: %d\r\n%s\r\n" % (
        code, CODE[code], cseq, extra)

def handle(txt):
    head = txt.split("\r\n")
    meth = head[0].split(" ")[0]
    cseq = 0
    for l in head[1:]:
        if l.lower().startswith("cseq:"):
            cseq = int(l.split(":", 1)[1])
    if meth == "OPTIONS":
        return resp(200, cseq, "Public: OPTIONS, DESCRIBE, SETUP, PLAY\r\n")
    return resp(405, cseq)

def serve(sock):
    c, _ = sock.accept()
    c.sendall(handle(c.recv(8192).decode()).encode())
    c.close()

srv = socket.socket()
srv.bind(("127.0.0.1", 0))
srv.listen(4)
port = srv.getsockname()[1]
threading.Thread(target=serve, args=(srv,), daemon=True).start()

for i, meth in enumerate(("OPTIONS", "JUMP"), 1):
    if i == 2:
        threading.Thread(target=serve, args=(srv,), daemon=True).start()
    s = socket.create_connection(("127.0.0.1", port), 3.0)
    s.sendall(("%s rtsp://127.0.0.1/live/test RTSP/1.0\r\n"
                "CSeq: %d\r\n\r\n" % (meth, i)).encode())
    print("--- 发 %s ---" % meth)
    print(s.recv(8192).decode().strip())
    s.close()

实跑输出:

--------------------------------------------------------------------
应答消息——状态码只有这么几个
--------------------------------------------------------------------
① 一条正常的应答

  | RTSP/1.0 200 OK
  | CSeq: 1
  | Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER

② 一条失败的应答

  | RTSP/1.0 405 Method Not Allowed
  | CSeq: 2

③ RTSP 常用的状态码

  码      意思                      什么时候出现
  ----------------------------------------------------------------
  200     OK                        一切正常
  400     Bad Request               报文格式错、CSeq 对不上
  401     Unauthorized              要认证
  404     Not Found                 这个 URL 没有对应的流
  405     Method Not Allowed        服务器不支持这个方法
  454     Session Not Found         Session 号过期或不认识
  461     Unsupported Transport     Transport 头里的协议不支持
  500     Internal Server Error     服务器自己出问题了

  **实测这一条是 405**——因为我们发了个不存在的 `JUMP` 方法。
  **注意 454「Session Not Found」是 RTSP 特有的:**
  **HTTP 没有会话这个概念,自然也没有这个错。**

第二条是 405,因为我们发了个标准里根本没有的 JUMP。注意服务器并没有崩,它只是礼貌地拒绝。 两条响应的 CSeq 分别是 1 和 2,和请求一一对应。这就是配对机制在真实报文里的样子。 失败响应只有两行,没有 Public、没有 Session。失败时服务器不给多余信息,这是常态。

30 秒实验

  1. 把 JUMP 换成 RECORD 再跑,还是 405——因为服务器只实现了 OPTIONS。体会一下「服务器只认自己实现的方法」。
  2. 在 resp() 里加一个 454 分支,让 RECORD 返回 454,看客户端代码要不要改——不用改,它只看数字。
  3. 把响应里的 CSeq: %d 改成固定的 CSeq: 0,想象现在的客户端还能不能正确配对。

常见误区

  1. 「状态码 200 就是成功」——SETUP 的 200 里如果没有 Session 头,后面照样播不了。要看「码 + 关键头域」两样。
  2. 「原因短语可以用来判断」——各家写法不统一,只认三位数字。
  3. 「没收到响应就是服务器挂了」——也可能是请求发得不完整、或者服务器在等更多数据(少了结尾的空行)。
自测三问(点开对答案) 1. 怎么判断一条应答后面有没有体? —— **看 `Content-Length`。**有就按长度读,没有就当体为空。DESCRIBE 的应答一定有。 2. 454 一般由哪三种情况引起? —— **忘了带 Session、Session 号抄错、会话已超时被回收。** 3. 收到 461 该怎么办? —— **换 `Transport` 重试**——把 TCP 换成 UDP 或反过来,不是改别的代码。

相关笔记