结合Wireshark抓包学习RTMP协议(下)

Published: 2020-08-06

Tags: RTMP

本文总阅读量

消息类型

参考文档:https://chenlichao.gitbooks.io/rtmp-zh_cn/content/7.1-message-types.html

客户端和服务端通过在网络上发送消息来实现交互,消息可以是任意类型,包括但不限于音频消息、视频消息、指令消息、共享对象消息、数据消息和用户控制消息。

请求连接到服务器实例

查看 Wireshark 抓包的数据,这里的 connect 就是一条指令消息。

指令消息:指令消息在客户端和服务端之间传递AMF编码的指令,消息类型20代表AMF0编码,消息类型17代表AMF3编码。发送这些消息来完成连接、创建流、发布、播放、暂停等操作。

上图橙色圈中十六进制的 0x14 即为十进制的 20,表示指令使用 AMF0 编码方式,还不了解这个编码,先跳过后续研究。

客户端通过向服务端发送 connect 指令来请求连接到服务器实例。指令 Body 具体的格式在 RTMP 英文文档中有描述,比如 "connect" 字符串是命令名称,抓包图中的 Number 1 对应的是 Transaction ID,根据协议需要设置为 1,命令对象是键值对。

app 指定连接到 RTMP 服务的应用名,比如地址为 “rtmp://localhost:1935/testapp/instance1”,其应用名为 testapp,更多的参数搜索 “RTMP connect” 查看解释。

Connect 交互流程

基本握手完成后,客户端发送 connnect 指令来请求连接到 RTMP 服务器实例,在发送指令之后,客户端和服务端会进行一些通信,通过 Wireshark 也会看到这几条消息记录,它们是作为一个整体的。

  1. 首先,客户端发送 connect 指令请求连接到 RTMP 服务器实例。
  2. 服务端收到 connect 指令后,向客户端发送协议控制消息 “Window Acknowledgement Size” 通知其应答窗口大小,并且服务端自己也连接到客户端 connect 指令消息体中提到的应用上。
  3. 服务端向客户端发送协议控制消息 “Set Peer Bandwidth(6)”,设置对端发送最大带宽。
  4. 客户端设置完带宽后,向服务端发送 “Window Acknowledgement Size” 通知。
  5. 服务端向客户端发送用户控制消息(StreamBegin),通知其可以进行推流。
  6. 服务端向客户端发送 connect 指令的执行结果,通知其连接状态(成功或失败),并携带一些信息。

在上边已经过了一遍客户端发送 connect 指令后的流程,接下来查看 Wireshark 抓包。

在第 10 号包服务端一起发送了不少数据,且先一条条看。

首先第一个是一个协议控制消息:Window Acknowledgement Size(5),这个在上篇中见到过,此处再查看一下文档关于其描述

The client or the server sends this message to inform the peer of the
 window size to use between sending acknowledgments. The sender
 expects acknowledgment from its peer after the sender sends window
 size bytes. The receiving peer MUST send an Acknowledgement
 (Section 5.4.3) after receiving the indicated number of bytes since
 the last Acknowledgement was sent, or from the beginning of the
 session if no Acknowledgement has yet been sent.

客户端或服务端发送这个消息到对端,告知其自己的窗口大小,当发送方已经发送了窗口大小字节数的数据后,它要求接收方回复 Acknowledgement(3)消息,接收方必须发送应答消息,否则发送方将暂停或停止发送。

接收方可以在会话开始的时候,或上一次发送应答之后接收到了等于窗口大小的数据之后发送 Acknowledgement(3)消息。

紧跟 Window Acknowledgement Size 消息之后的是用户控制消息 Stream Begin 0,表示可用流的ID,再之后是设置对端最大发送带宽(2500000,2.5兆,320Kb/s),这几个之前都或多或少见过了,服务端为了减少请求次数提升效率,一条 RTMP消息中携带了多条指令一次性发送到客户端。

值得注意的是消息中的第4块 —— “AMF0 Command onBWDone()”。

我没在 RTMP 协议中找到它的描述,在网上 ActionScript 3.0 里看到了它的说明:链接

The onBWCheck() function is required by native bandwidth detection. It takes an argument, ...rest. The function must return a value, even if the value is 0, to indicate to the server that the client has received the data. You can call onBWCheck() multiple times.

The server calls the onBWDone() function when it finishes measuring the bandwidth. It takes four arguments. The first argument is the bandwidth measured in Kbps. The second and third arguments are not used. The fourth argument is the latency in milliseconds.

结合RTMP协议,意思是说当服务端检测完带宽完成后调用客户端的 onBWDone() 方法,然后客户端调用服务端的 onBWCheck() 方法告知服务端它接收到了消息(在抓包时看到的 _checkbw() 应该就是这个确认的调用)。

这里我翻了半天,资料感觉很少,在【参考1】链接中的有提及,后续继续学习 RTMP 客户端/服务端实现的时候再进一步验证目前的理解对不对。(忽略上边截图中的_result(),它不属于带宽确认环节的响应)

服务端调用客户端的 _result('NetConnection.Connect.Success') 方法,这个对应的就是本小节最开始梳理 connect 流程时的最后一步了 —— “服务端向客户端发送 connect 指令的执行结果,通知其连接状态(成功或失败),并携带一些信息。”

携带的信息暂且略过,显然这是一个流程执行成功的回复,暂时知道这些就够了~

推流的准备

经过起初的握手和连接,客户端可以进行推流或拉流操作,本例是推流的抓包,是把视频推送到服务器。

抓包可知 FFmpeg 将 releaseStream, FCPublis, createStream 三个指令一起发到服务端。

releaseStream 表示释放 stream,这一步的作用是通知服务端释放对应的 stream,以便接下来重新创建 stream。

FCPublish 有什么作用我没有查到,在 libav 提交记录中,有提及到 FCPublish 与 _checkbw ,标题是这样说的——“rtmp: Gracefully ignore FCPublish and _checkbw errors”(优雅地忽略 FCPublish 与 _checkbw 错误)

提交描述部分描述如下:

Those messages are adobe specific historical artefacs that some rtmp servers do not support. It is safer to ignore their failure. This fixes publishing streams to crtmpserver and it fixes bugzilla bug #309.

这些消息是 adobe 特定历史文物,一些 rtmp 服务器并不支持,忽略他们的报错就好。

虽然不知道最初 adobe 用这些函数做什么了,不过看起来现在已经没有具体的用途了,无需进一步了解。

createStream

The client sends this command to the server to create a logical channel for message communication The publishing of audio, video, and metadata is carried out over stream channel created using the createStream command. NetConnection is the default communication channel, which has a stream ID 0. Protocol and a few command messages, including createStream, use the default communication channel.

客户端向服务端发送 createStream 指令来创建逻辑通道,用于发送音频,视频,元数据消息。

在 RTMP 文档中有关于客户端发起 createStream 的消息体说明:第一个字段为命令名称,也就是 “createStream”,第二个字段为命令的事务ID,第三个字段为命令相关信息,如果没有则为空。

还是上图,可以看到紧跟在 createStream 消息后的是服务器回复的 _result() 消息,它的内容如下:

第一个字段内容为 “_result”,第二个字段为事务ID,此处值为5,跟 createStream 的事务ID值一样,表示这个消息是对 createStream 的回复, 同样的第三个字段为命令的补充信息,此处为空,回复多了第四个字段,回复了一个 Stream ID,值为1。

Stream ID 为 0 是默认消息通道,比如 createStream 就是在这个逻辑通道上通信的。

接下来,客户端发送 publish 指令设置推流的名称,通过流的名称,其它客户端能够连接到 RTMP 服务进行拉流播放。

一个 RTMP 推流地址形如:“rtmp://192.168.3.188:1935/live/stream”,这个 “stream” 就是流名称。

抓包过程中的 publish("stream") 参数设置的也是这个流名称 “stream”

命令消息体中的参数同理,第一个参数 “publish” 字符串为命令名称,Number 6 为事务ID,第三个字段 Command Object 没有值为空,第四个字段为推送的流名称,最后一个字段为推送类型,如果值为 “record” 则 RTMP 服务器将流写入到文件中保存,已经存在同名文件会覆盖,如果值为 “append”,则将流追加到文件中进行保存,文件不存在则创建,本例值 “live”,服务器不写入文件,直接发布。

值得注意是的,publish 指令的 Header 头可以看到,指令消息走的是 Stream ID = 1 的通道。

在客户端通知服务端推流名称后,服务端向客户端发送 “User Control Message(0x04)” 用户控制指令,上图中的“Stream Begin 1”,这条指令消息在上篇的时候已经了解过了。

服务端在通知用户端这个流已经可以用来通信后,紧接着又向客户端发送 onStatus('NetStream.Publish.Start') 命令。

这个网址可以看查询到 NetStream 具体的指令含义:https://helpx.adobe.com/adobe-media-server/ssaslr/netstream-class.html

"NetStream.Publish.Start" 表示 “An attempt to publish was successful.”(尝试发布成功。)

这里需要简单了解下 NetStream 命令。

RTMP协议规定,播放一个流媒体有两个前提步骤:第一步,建立一个网络连接(NetConnection);第二步,建立一个网络流(NetStream)。其中,网络连接代表服务器端应用程序和客户端之间基础的连通关系。网络流代表了发送多媒体数据的通道。服务器和客户端之间只能建立一个网络连接,但是基于该连接可以创建很多网络流。播放一个RTMP协议的流媒体需要经过以下几个步骤:握手,建立连接,建立流,播放。RTMP连接都是以握手作为开始的。建立连接阶段用于建立客户端与服务器之间的“网络连接”;建立流阶段用于建立客户端与服务器之间的“网络流”;播放阶段用于传输视音频数据。

服务端向客户端表明 “网络流已经创建成功” 和 “尝试发表成功” 后,客户端可以开始推送数据。

推送视音频流

首先的一条消息是 @setDataFrame(),在这篇文档我找到关于 @setDataFrame 的说明

https://helpx.adobe.com/adobe-media-server/dev/adding-metadata-live-stream.html

Sending metadata to a live stream To send metadata to clients when they connect to a live stream, pass a special command, @setDataFrame

可知这条消息是客户端向服务端发送了关于流媒体的元数据信息,为了高效的利用线路,后边还带了一堆 Video Data | Audio Data | Video Data

随便查看一个 Video Data

是一个 H.264 编码的关键帧数据。其实通过 Wireshark 抓包查看 RTMP 的通信流程,到这里的目的已经达到了

  • 了解了推流时客户端与服务端的通信步骤
  • 了解了RTMP推流过程中的指令,消息都是干什么的
  • 为后续进一步阅读RTMP服务端或客户端代码打下基础

H.264 的编码在之前有过了解,后续再深入研究。先到这里~~~

补充

1)Wireshark 抓包过滤命令在上篇写的蛮麻烦的,而且过滤效果不好,看到一篇 blog,作者用的 “rtmpt” 过滤的...

很简单粗暴的小技巧,学到了。

2)Wireshark 中带如 createStream(), _result(),都是 RPC 远程过程调用。

Remote Procedure Call (RPC): A request that allows a client or a server to call a subroutine or procedure at the peer end.

服务器和客户端可以调用对端的方法,传递数据、消息,有点儿编程语言中 CallBack 回调的感觉。

参考:

  1. A Peek Inside Adobe's Real Time Messaging Protocol (RTMP)
  2. rtmp信令交互过程分析四-发布(publish)
  3. rtmp: Gracefully ignore FCPublish and _checkbw errors
  4. RTMP协议播放流程的实现及抓包分析