通过-wireshark-抓包了解直播流媒体-RTMP-协议基本过程

32次阅读

共计 2742 个字符,预计需要花费 7 分钟才能阅读完成。

作者:Elias Zhang 声网资深工程师,拥有从 Iaas 层的基础信息存储服务到 paas 层的云服务的职业经历,喜欢 python 语言,习惯使用 C#,熟悉基于和结合 CDN 的业务产品架构,点播、直播、云导播等。喜欢探索问题和研究创新,拥有 5 项国家发明专利。

在直播是最常见的实时音视频场景,而 RTMP 是该场景下最重要的协议之一,是很多初步接触实时音视频的开发者需要了解的。本文会一边利用 winshark 工具进行抓包,一边从中分析 RTMP 协议的基本原理,帮助大家更容易地理解它。

先给出 RTMP 协议的原文件 https://www.adobe.com/devnet/… 需要用到的时候可以参考一下~。

做推流直播接触最多的并且最主要是 RTMP 协议

  • RTMP 协议是应用层协议,是要靠底层可靠的传输层(TCP)
  • 协议(通常是 TCP)来保证信息传输的可靠性的。在基于传输层协议的链接建立完成后,RTMP 协议也要客户端和服务器通过“握手”来建立基于传输层链接之上的 RTMP Connection 链接. 播放一个 RTMP 协议的流媒体需要经过以下几个步骤:握手,建立网络连接,建立网络流,播放。服务器和客户端之间只能建立一个网络连接,但是基于该连接可以创建很多网络流。他们的关系如图所示:

  • RTMP 协议传输时会对数据做自己的格式化,这种格式的消息我们称之为 RTMP Message,而实际传输的时候为了更好地实现多路复用、分包和信息的公平性,发送端会把 Message 划分为带有 Message ID 的 Chunk,每个 Chunk 可能是一个单独的 Message,也可能是 Message 的一部分,在接受端会根据 chunk 中包含的 data 的长度,message id 和 message 的长度把 chunk 还原成完整的 Message,从而实现信息的收发。

协议本身的详细字段和流程就不在这里详细解释了,主要结合看包直观的了解下这个协议的流程,更详细的内容可以查阅前面给出的官方文档。

RTMP 步骤:

1. 握手

要建立一个有效的 RTMP Connection 链接,首先要“握手”: 客户端要向服务器发送 C0,C1,C2(按序)三个 chunk,服务器向客户端发送 S0,S1,S2(按序)三个 chunk,然后才能进行有效的信息传输。RTMP 协议本身并没有规定这 6 个 Message 的具体传输顺序,但 RTMP 协议的实现者需要保证这几点:

  • 客户端要等收到 S1 之后才能发送 C2
  • 客户端要等收到 S2 之后才能发送其他信息(控制信息和真实音视频等数据)
  • 服务端要等到收到 C0 之后发送 S1
  • 服务端必须等到收到 C1 之后才能发送 S2
  • 服务端必须等到收到 C2 之后才能发送其他信息(控制信息和真实音视频等数据)

握手开始于客户端发送 C0、C1 块。服务器收到 C0 或 C1 后发送 S0 和 S1。
当客户端收齐 S0 和 S1 后,开始发送 C2。当服务器收齐 C0 和 C1 后,开始发送 S2。
当客户端和服务器分别收到 S2 和 C2 后,握手完成。

实际上真实发包如下:

我们可以看见 TCP 的三次握手,RTMP 基于 TCP 的可靠传输。

接下去过滤 rtmpt 协议,rtmp 的握手过程如下,我们发现真实发包是 C0+C1 一起发;S0,S1,S2 一起发。

2. 建立网络连接(NetConnection)

a) 客户端发送命令消息中的“连接”(connect)到服务器,请求与一个服务应用实例建立连接。

打开 connect 这个包,一个 OSI5 层协议模型,最后一个是 RTMP 协议发送了 connect 链接消息,查看内容包含推流地址名,但是可以观察到还没有发流名,地址是有 app 名。

观察一下 RTMP 的包头

StreamID 是每个消息的唯一标识,划分成 Chunk 和还原 Chunk 为 Message 的时候都是根据这个 ID 来辨识是否是同一个消息的 Chunk 的,这里面为 0 说明这个消息是初始的 0 消息。

Chunk stream ID:message 会拆分成多个 chunk,同一个 Chunk Stream ID 必然属于同一个 Message。

message type id(消息的类型 id):表示实际发送的数据的类型,如 8 代表音频数据、9 代表视频数据。

Format:指的是 chunk type。共有 4 种不同的格式,其中第一种格式字段为 0,可以表示其他三种表示的所有数据,但由于其他三种格式是基于对之前 chunk 的差量化的表示,因此可以更简洁地表示相同的数据,实际使用的时候还是应该采用尽量少的字节表示相同意义的数据。因为 type0 是表示不同数据,其他是差量,所以可以想象如果搜不到 type0 的包说明这个流肯定有问题。可以通过“rtmpt.header.format == 0”过滤。

b) 服务器接收到连接命令消息后,发送确认窗口大小 (Window Acknowledgement Size) 协议消息到客户端,同时连接到连接命令中提到的应用程序。

c) 服务器发送设置带宽协议消息到客户端。

d) 客户端处理设置带宽协议消息后,发送确认窗口大小 (Window Acknowledgement Size) 协议消息到服务器端。

e) 服务器发送用户控制消息中的“流开始”(Stream Begin)消息到客户端。

f) 服务器发送命令消息中的“结果”(_result),通知客户端连接的状态。
b~f 如图:在_result 我们可以看到链接已经建立成功

接下去的包我们看到发了 releaseStream 命令,里面的 agora 就是流名,所以一个推流地址我们可以抓包 connect 和 releaseStream 里面拼接得出。

  1. 建立一个网络流(NetStream)

提示:网络流代表了发送多媒体数据的通道。服务器和客户端之间只能建立一个网络连接,且多个网络流可以复用这一个网络连接。

a. 客户端向服务器发送请求创建流(createStream)。

b. 服务器收到请求后向客户端发送_result(),对创建流的消息进行响应。此时 NetStream 创建完成。

4. PLAY 播放

a) 客户端发送命令消息中的“播放”(play)命令到服务器。

b) 接收到播放命令后,服务器发送设置块大小(ChunkSize)协议消息。

c) 服务器发送用户控制消息中的“streambegin”,告知客户端流 ID。

d) 播放命令成功的话,服务器发送命令消息中的“响应状态”NetStream.Play.Start & NetStream.Play.reset,告知客户端“播放”命令执行成功。

e) 在此之后服务器发送客户端要播放的音频和视频数据。

可以注意到里面音频 type 是 8,视频是 9。

5. PUBLISH 推流


推流从握手开始和前面步骤 123 一致。

和第四步 play 区别在于 netstream 的命令改为 publish

关于本文,如果你在跟随步骤操作或阅读时有任何疑问,请点击这里并留言,可与作者直接交流。

正文完
 0