RTMP 是跑在 TCP 上的直播协议,用 1935 端口,延迟一到三秒。 它比 HLS 快一个数量级,秘密不在传输层,而在「不攒够一个切片就发」。
第三章讲的 RTSP 是「遥控器搬数据」的老路子。从这一章起换成一个更工业的选择:直播行业的事实标准 RTMP。 4.2 拆握手与命令,4.4 拆块流,这一节先定性。
一、它跟 RTSP 的根本区别
两个协议都能推能拉、都能低延迟,但设计目标完全不同。 ① RTSP 是「点播的延伸」:它假设有一个文件、有一个时间轴,你可以随意暂停与拖动。 ② RTMP 是「推流的工作台」:它假设你有一个采集端在不停往外吐,重点是「怎么把这一堆帧稳定地送出去」。
这个差别带来一个直接后果:RTMP 把音视频的封装格式也定死了。 它的消息体就是 FLV 的 tag 数据体,所以「RTMP 封 FLV」其实是「共用同一套负载格式」,封装层与协议层是一起设计的。
RTSP 反过来是「只管命令、不管封装」,SDP 里说什么格式就发什么格式。 灵活,但也意味着每个环节都要自己谈一遍。
二、一个端口搞定所有事
RTMP 只用一条 TCP 连接、一个端口 1935。 命令、音频、视频、控制信息全在这一条连接里跑,靠「块流 ID」区分。
这跟 RTSP 的「一个固定端口 + 一堆临时 UDP 端口」正好相反。 好处是穿防火墙极容易——只要放行 1935 就行,不像 RTSP 还要放一段 UDP 区间。 代价是「大家都挤在一条 TCP 上」: TCP 保证有序,所以只要一个包丢了、在等重传,后面所有流都得排队。 这就是队头阻塞,也是 RTMP 延迟下不去的根本原因。
那为什么不换 UDP? 因为 RTMP 定型在 2002 年前后,那时的网络与企业环境里「能穿过去」比「延迟低 200 毫秒」重要得多。 这个取舍一直留到今天。
三、延迟到底花在哪
一拍脑袋以为「用 TCP 就慢」,其实不是。 把一条 RTMP 链路的延迟拆开,大头往往不在协议本身: ① 编码器缓冲:为了压码率稳定,编码器常攒几帧再出,200~500 毫秒。 ② 等关键帧:播放端必须从 IDR 开始,最坏要等一个 GOP,1~4 秒,这是最大的一块。 ③ 网络传输:同城通常 20~50 毫秒,跨省 50~100 毫秒。 ④ 播放器缓冲:攒几帧再播,200~500 毫秒。
四段加起来才是你看到的「三秒」。 其中协议本身贡献的只有百分之几——所以「换成低延迟协议」往往只能省下最后一小段,先把 GOP 与缓冲调小效果更明显。
四、RTMP 现在还在用吗
【⚠️ 2026 现状】Flash 已经在 2020 年底彻底停用,但 RTMP 协议本身活得很好。
原因是它被「降级使用」了: ① 推流端几乎全是 RTMP:OBS、FFmpeg、各种采集卡软件默认就推 RTMP。 平台收流的第一跳仍然是 1935。 ② 播放端基本不用 RTMP 了:浏览器不支持(要 Flash),所以拉流换成了 HTTP-FLV、HLS、WebRTC。 ③ 服务端之间常用更低成本的转发:RTMP 或 SRT。
所以今天讲 RTMP,讲的多半是「推流这一段」。
你打开 OBS 填的那个 rtmp:// 地址,走的就是这一章的全部内容。
而观众那边看到的是 HTTP-FLV 或 HLS——那是第五、六章的事。
五、动手 Demo:一个端口、一条连接、一堆消息
先把三个协议摆在一起对比,再真跑一次「握手包」的递交。 握手只是确认「对方懂 RTMP」,不做认证。
import socket, struct, os, threading
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(("127.0.0.1", 0)) # 真实环境是 1935
srv.listen(4)
port = srv.getsockname()[1]
got = {}
def serve():
c, _ = srv.accept()
data = b""
while len(data) < 1537: # C0 1 字节 + C1 1536 字节
d = c.recv(4096)
if not d:
break
data += d
got["in"] = len(data)
got["c0"] = data[0]
# S0 + S1:版本号 + 时间戳 4 + 零 4 + 随机 1528
c.sendall(struct.pack("!B", 3) + b"\x00" * 8 + os.urandom(1528))
c.close()
threading.Thread(target=serve, daemon=True).start()
c = socket.create_connection(("127.0.0.1", port), 3.0)
c1 = struct.pack("!I", 0) + b"\x00" * 4 + os.urandom(1528)
c.sendall(struct.pack("!B", 3) + c1)
s0s1 = b""
while len(s0s1) < 1537:
d = c.recv(4096)
if not d:
break
s0s1 += d
c.close()
srv.close()
print("客户端递出 %d 字节,C0 版本号 = %d" % (got["in"], got["c0"]))
print("服务器回了 %d 字节,S0 版本号 = %d" % (len(s0s1), s0s1[0]))
print("完整握手六个包共 3073 字节")
实跑输出:
--------------------------------------------------------------------
RTMP 简介——一个端口、一条连接、一堆消息
--------------------------------------------------------------------
**① 先把三个协议摆在一起看**
协议 传输 端口 典型延迟
---------------------------------------------
RTMP TCP 1935 1~3 秒
RTSP TCP+UDP 554 0.5~2 秒
HLS HTTP 80/443 6~30 秒
**RTMP 的定位很特别:它用 TCP(可靠、会重传),但延迟又比 HLS 低一个数量级。**
**秘密在「不做缓冲」:收到就往上送,不像 HLS 要攒够一个切片才发。**
**② 真跑一遍:连上 1935 风格的端口,递出握手包**
方向 包 字节 内容
---------------------------------------------------------------
客户端→服务器 C0 1 版本号 3
客户端→服务器 C1 1536 时间戳 0 + 零 + 1528 随机
服务器→客户端 S0 1 版本号 3
服务器→客户端 S1 1536 时间戳 + 零 + 随机
**实测客户端递了 1537 字节,服务器回了 1537 字节。**
**完整握手是六个包,一共 3073 字节**(C0+C1+C2 与 S0+S1+S2 各半)。
**③ 为什么是 1536 这个数**
**1536 = 4(时间戳)+ 4(必须为零)+ 1528(随机)。**
**凑成 1536 是为了让 C1 正好等于「一个 1500 字节的 MTU 再加一点」,旧实现一次写完不用拆包。**
**这个数从 Flash 时代定下,一直没改。**
**握手的作用只有一个:双向确认「对方懂 RTMP、且版本兼容」。**
**它不做认证、不交换参数——认证在后面的 connect 命令里。**
C0 只有 1 字节,就是版本号,现代实现固定写 3。 C1 是 1536 字节:时间戳 4 + 必须为零的 4 字节 + 1528 字节随机。 完整握手六个包、3073 字节——但它只确认「双方懂这套协议」,认证在后面的 connect 里。
30 秒实验
- 把 C0 的版本号从 3 改成 2,在真服务器上试一次——多数实现会直接断开,这就是「连上就断」的典型。
- 把 C1 的随机段改成 1528 个 0,再看握手能不能过——能过,但服务器端统计里会记录「弱随机」,属于隐患。
- 用
telnet 127.0.0.1 1935试连一次,确认端口是通的——这是排除「网络问题」最快的一步。
常见误区
- 「RTMP 是 Flash 的协议,Flash 死了它就没了」——推流端几乎全是 RTMP,OBS 至今默认就推它。
- 「RTMP 延迟低是因为用了 UDP」——恰恰相反,它跑在 TCP 上,延迟主要来自等关键帧与缓冲。
- 「RTMP 也封 FLV,所以它俩是一回事」——协议层与封装层是两件事,只是共用了 tag 数据体这个负载格式。
自测三问(点开对答案)
1. RTMP 用哪个端口、跑在什么传输层上? —— **1935,TCP。**所有数据走同一条连接。 2. RTMP 相比 RTSP 在穿防火墙上有什么优势? —— **只用一个固定端口**,不像 RTSP 还要放一段 UDP 端口区间。 3. RTMP 的延迟大头在哪? —— **等关键帧与编码器、播放器的缓冲。**协议本身贡献很小。- ☐ (本节完,下一节 4.2.1 RTMP握手)