🎬 第七章 流媒体开源库简介
📄 一句话概括

Live555 只做一件事:用单线程事件循环跑 RTSP 服务。 没有多线程、没有锁,简单到可预测。

📝 本节和前面几节的关系

7.1 讲的是通用库,从这一节开始看专用实现。 7.3 转向 VLC,那是「播放端」的代表。

一、核心只有一个:任务调度

所有事情都注册成「延时任务」塞进一个队列, 主循环不停取最早的那个执行。 没有多线程、没有锁——这是 Live555 最鲜明的特点。 好处是简单、可预测;坏处是一个任务卡住,全部卡住。

它把「什么时候发下一包 RTP」也做成了延时任务: 一路流就是一条不断自我续期的任务链。 发完这包就把下一包的任务再注册进去,如此循环。

这个设计的好处是可预测: 任何时刻只有一件事在跑,不存在「两个线程抢同一路流」的问题。 所以它跑几年都不会出莫名其妙的并发 bug。

二、真发一条 RTSP 请求

实测客户端发 51 字节:OPTIONS rtsp://... RTSP/1.0 加 CSeq: 1 加空行。 服务器回 78 字节:RTSP/1.0 200 OK 加 CSeq 加一个 Public 头。

Public 头是 OPTIONS 的专用回复,它列出「这个服务支持哪些方法」。 客户端拿到它就知道能不能 PLAY、能不能 TEARDOWN。

注意 CSeq 必须原样回—— 它是「命令序号」,客户端靠它把响应和请求对上。 不回或者回错,客户端会认为这条命令没被处理。

三、它撑得住很多路的原因

因为它几乎不阻塞:发一包 RTP 的耗时是微秒级, 一个循环一秒能转几万次,几十路流的任务分摊下去毫无压力。

真正的瓶颈在带宽与磁盘,不在 CPU 调度。 这一条对整个流媒体领域都成立: 媒体服务器的 CPU 通常闲得很,卡的都是网络出口与存储带宽。

但单线程也有明确的短板: 如果某一步做了阻塞动作(比如同步读文件、做 DNS 查询), 整台服务器就停了。 所以基于 Live555 做二次开发,第一原则是「绝不阻塞主循环」。

四、动手 Demo:Live555——单线程事件循环撑起 RTSP 服务

先讲清单线程任务调度这个核心,再真起一个最小 RTSP 服务器。 看一条 OPTIONS 请求与它的回复各有多少字节。

import socket, threading

srv = socket.socket()
srv.bind(("127.0.0.1", 0))
srv.listen(1)
port = srv.getsockname()[1]
got = {}

def serve():
    c, _ = srv.accept()
    d = b""
    while b"\r\n\r\n" not in d:
        d += c.recv(4096)
    got["req"] = d
    body = ("RTSP/1.0 200 OK\r\n"
            "CSeq: 1\r\n"
            "Public: OPTIONS, DESCRIBE, SETUP, PLAY, TEARDOWN\r\n"
            "\r\n")
    c.sendall(body.encode())
    c.close()

th = threading.Thread(target=serve, daemon=True)
th.start()
s = socket.create_connection(("127.0.0.1", port), timeout=3)
s.sendall(b"OPTIONS rtsp://127.0.0.1/live RTSP/1.0\r\nCSeq: 1\r\n\r\n")
resp = s.recv(4096)
s.close()
th.join(3)
srv.close()
print("请求 %d 字节" % len(got["req"]))
print(got["req"].decode())
print("响应 %d 字节" % len(resp))
print(resp.decode())

实跑输出:

--------------------------------------------------------------------
Live555——单线程事件循环撑起 RTSP 服务
--------------------------------------------------------------------
**① 它的设计只有一个核心:单线程任务调度**

  **所有事情都注册成「延时任务」塞进一个队列,主循环不停取最早的那个执行。**
  **没有多线程、没有锁——这是 Live555 最鲜明的特点。**
  **好处是简单、可预测;坏处是一个任务卡住,全部卡住。**

**② 真起一个最小 RTSP 服务器,收一条请求**

  **客户端发 51 字节:**
    OPTIONS rtsp://127.0.0.1/live RTSP/1.0
    CSeq: 1
    (空行)

  **服务器回 78 字节:**
    RTSP/1.0 200 OK
    CSeq: 1
    Public: OPTIONS, DESCRIBE, SETUP, PLAY, TEARDOWN
    (空行)

  **Live555 做的就是这个:收到请求、按方法分派、回响应。**
  **区别是它把「什么时候发下一包 RTP」也做成了延时任务,**
  **所以一路流就是一个不断自我续期的任务。**

**③ 为什么它撑得住很多路**

  **因为它几乎不阻塞:发一包 RTP 的耗时是微秒级,**
  **一个循环一秒能转几万次,几十路流的任务分摊下去毫无压力。**
  **真正的瓶颈在带宽与磁盘,不在 CPU 调度。**

51 字节的请求只用了两个头,RTSP 的请求比 HTTP 还短。 78 字节的响应里 Public 头占了一半,它是 OPTIONS 的核心产出。 CSeq 原样返回 1,这是命令与响应配对的唯一依据。

30 秒实验

  1. 把 CSeq 改成 2 再发,看响应里的 CSeq——服务端(我们这个假的)会照抄,真实服务器也是照抄。
  2. 把方法从 OPTIONS 改成 PLAY,看回复——真实服务器会要求先 SETUP,这就是 RTSP 的状态性。
  3. 在 serve() 里加 time.sleep(1),想想后果——单线程下整台服务器停一秒,这就是「绝不阻塞主循环」的意义。

常见误区

  1. 「Live555 是多线程的」——单线程事件循环,这是它的立身之本。
  2. 「RTSP 请求必须带 Session」——只有 PLAY 之后才需要。
  3. 「CSeq 可以随便填」——响应必须原样回,否则配不上对。
自测三问(点开对答案) 1. Live555 的并发模型是什么? —— **单线程事件循环加延时任务队列**,没有多线程也没有锁。 2. OPTIONS 的回复里哪个头最关键? —— **`Public`**,它列出服务端支持的方法。 3. 基于它二次开发的第一原则? —— **绝不阻塞主循环**,任何阻塞动作都会拖停全部流。

相关笔记