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 秒实验
- 把
CSeq改成 2 再发,看响应里的 CSeq——服务端(我们这个假的)会照抄,真实服务器也是照抄。 - 把方法从 OPTIONS 改成 PLAY,看回复——真实服务器会要求先 SETUP,这就是 RTSP 的状态性。
- 在
serve()里加time.sleep(1),想想后果——单线程下整台服务器停一秒,这就是「绝不阻塞主循环」的意义。
常见误区
- 「Live555 是多线程的」——单线程事件循环,这是它的立身之本。
- 「RTSP 请求必须带 Session」——只有 PLAY 之后才需要。
- 「CSeq 可以随便填」——响应必须原样回,否则配不上对。
自测三问(点开对答案)
1. Live555 的并发模型是什么? —— **单线程事件循环加延时任务队列**,没有多线程也没有锁。 2. OPTIONS 的回复里哪个头最关键? —— **`Public`**,它列出服务端支持的方法。 3. 基于它二次开发的第一原则? —— **绝不阻塞主循环**,任何阻塞动作都会拖停全部流。- ☐ (本节完,下一节 7.3.1 VLC 播放器)