流媒体学习2
四、RTMP详解
RTMP是Real Time Messaging Protocol(实时消息传输协议)的首字母缩写。该协议是应用层协议,基于TCP。RTMP是一种设计用来进行实时通信的网络协议,主要用来在Flash平台和支持RTMP协议的流媒体/交互服务器之间进行以视频和数据通信。直播场景中使用RTMP协议比较多。
1.握手
RTMP协议是基于TCP的,TCP建立连接有三次握手。如下图:
所以,RTMP首先在TCP层面是有三次握手的过程的,在TCP连接建立以后,再进行RTMP协议层次的握手。
1)握手的简单流程
在rtmp连接建立以后,服务端要与客户端通过3次交换报文完成握手。与其他握手协议不同,rtmp协议握手交换的数据报文是固定大小的,客户端向服务端发送的3个报文为c0、c1、c2,服务端向客户端发送的3个报文为s0、s1、s2。c0与s0的大小为1个字节,c1与s1的大小为1536个字节,c2与s2的大小为1536个字节。
发送顺序
- 建立连接后,客户端开始发送C0、C1块到服务器;
- 服务器端收到C0或C1后发送S0和S1;
- 当客户端收齐S0和S1之后,开始发送C2;
- 当服务端收齐C0和C1后,开发发送S2;
- 当客户端收到S2,服务端收到C2,握手完成。
在实际工程应用中,一般是客户端将C0、C1块同时发出,服务器在收到C1块之后同时将S0、S1、S2发给客户端。客户端收到S2之后,发送C2给服务端,握手完成。
2)握手数据包格式
C0和S0
C0和S0数据包占用一个字节,表示RTMP版本号。目前RTMP版本定义为3,0-2是早期的专利产品所使用的值,现已经废弃,4-31是预留值,32-255是禁用值。
C1和S1![https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EVFMyNHVoMnJzaWJubjQ4cXhJYXIwSnNubU5LV3VBMTZnWnJPd0lJZ3JpY2lhNGFPVjE3WmwxekEyckFMVnNMcFRjZTFpYVlMTTJsSExCa2cvNjQw?x-oss-process=image/format,png](https://img2023.cnblogs.com/blog/2525040/202312/2525040-20231208135901543-467561700.png)
C1和S1数据包占用1536个字节。包含4个字节的时间戳,4个字节的0和1528个字节的随机数。
C2和S2
C2和S2数据包占用1536个字节,包含4个字节的时间戳,4个字节的对端的时间戳(C2数据包为S1数据包的时间戳,S2为C1数据包的时间戳)。
3)几种握手的状态
未初始化
协议版本被发送,客户端服务端都是未初始化的,客户端在C0数据包中发送协议版本,如果服务器支持这个版本,将会发送S0和S1作为响应,如果不支持,则服务端会用响应的动作来响应,在RTMP中这个动作是结束这个连接。
版本发送完成
客户端和服务端在未初始化状态之后都进入到版本发送完成状态,客户端等待包S1,服务端等待包C1,在收到相应的包后,客户端发送包C2,服务端发送包S2,状态变成询问发送完成。
询问发送完成
客户端和服务端等待S2和C2。
握手完成
客户端和服务端开始交换信息。
抓包
注意,wireshart过滤RTMP协议的条件是rtmpt,不要忘了后面的t。
4)抓包实战
- source为客户端(192.17.1.92),握手时首先向server(192.17.1.200)发送C0和C1数据包;
- 服务端收到C0和C1数据包之后,直接发送S0+S1+S2数据包;
- 客户端收到服务端发来的S2数据包后,发送C2数据包,至此,rtmp握手完成,可以进行数据交换了。
C0和C1数据包
S0+S1+S2的数据
C2的数据
握手的过程主要完成了两个工作,一是对rtmp的版本进行校验,二是发送了一些随机数据,用于网络状况的检测。握手成功之后,表示客户端和服务器之间可以正常进行网络通信,接下来就可以进行数据的交互了。
2.rtmp Header
Rtmp协议握手完成之后,就可以进行数据交互了,但交换的数据格式需要一个组织的标准,发送端按照该标准进行数据的组装,接收方按照该标准进行数据的拆解,这样才能完成通信。rtmp的协议的数据包,总的来讲分为两大部分,一部分是Rtmp Header,另一部分为Rtmp Body。
1)RTMP header数据包的长度
RTMP header的长度不固定,可能的长度为12字节,8字节,4字节,1字节。具体长度为多少个字节,由RTMP header数据包的第一个字节的高2位决定。
如下方表格,Format决定了RTMP header的长度为多少个字节:
下面,通过wireshark抓包看下,RTMP HEADER的长度。
图中,RTMP Header的第一个字节为0x03,高两位的值为00,所以,整个RTMP Header的长度就是12个字节了。
2) Chunk Stream ID
第一个字节的低6位,命名为Chunk Stream ID,Chunk Stream ID用来表示消息的级别:
chunk_stream id为3,表示消息的级别为high levle,这一条消息实际上是一条connect的消息。
知道了RTMP header的第一个字节的作用以后,接下来我们看下几种不同长度的RTMP Header。
3)12字节的RTMP Header
- 第一个时间戳三个字节。
- BodySize字段,表示RTMP Body所包含数据包的大小,此处为209,可以在图中数一数,除去RTMP Header部分,后面的数据部分长度便是209。
- Type ID字段表示消息类型ID,比如此处0x14表示以AMF0编码(还有AMF3编码,Adobe定义的编码方式)。另外还有如0x04表示用户控制消息,0x05表示Window Acknowledgement Size,0x06表示 Set Peer Bandwith等等,就不一一列举了。
- 还有最后一个Stream ID,Stream ID通常用以完成某些特定的工作,如使用ID为0的Stream来完成客户端和服务器的连接和控制,使用ID为1的Stream来完成视频流的控制和播放等工作。
4)8字节的RTMP Header
- 第一个字节,高2位为01,所以RTMP Header的长度为8字节
- 接下来是时间戳的delta,简单讲就是时间戳的变化量
- BodySize不多说
- TypeID此处为0x04,也就是用户控制信息(本条消息实际上是rtmp客户端和服务器之间的Ping Response)。
5)4字节的RTMP Header
4字节的就比较简单了,除了第一个字节之外,只有一个关于时间戳的增量,占用3个字节。可以看到,第一个字节为0xa2,所以高2位的值为10,所以,RTMP Header占用4个字节,后面跟着的时间戳的增量。
6)1字节的RTMP Header
一个字节,就是第一个字节,后面啥也没有喽,高2位为11,所以,RTMP Header占用1个字节,只包含Format 和chunk stream ID。
3.Rtmp Body
说到RTMP Body的数据包组织格式,就不得不提到AMF。AMF英文全称Action Message Format,是Adobe定义的一套用来进行数据打包的格式,主要的版本有AFM0和AMF3,不过发展至今,实际场景中AMF0一直用的比较多,AMF3相对少见,本篇就以AMF0为例来讲解了。
那么AMF和RTMP Body又有什么关系呢,不才,RTMP数据包的序列化就是按照AMF的格式进行的。RTMP的客户端和RTMP的服务端约定好,发送方说,我发送你的数据都是按照某种格式组织的,你如果收到了我发给你的数据包,你就按这个数据包格式进行解析就可以了。这个格式就是我们此处说的AMF。
1)核心的类型
先说下AMF0,这一标准定义了16种核心的类型,如下表
AMF定义中,首先使用一个字节来表示数据类型,可选的核心数据类型如上表。在数据类型后面紧跟着的就是对应类型数据的长度,每一种类型长度字段所占用的字节数可能也不尽相同,比如string类型后面紧跟的是后面字符串的长度,长度占用2个字节,在长度后面就是具体的数值,还是拿string来举例,长度后面就是具体的字符数据。本质上,这也是一种类似key-len-val(也有叫type-len-val)的一种数据组织方式。
不过,AMF中对于某些类型的数据,没有长度的选项,因为AMF中规定了该类型所占据的字节长度大小。比如AMF规定,number类型所占用的长度为8字节,其表示方式是双精度浮点的网络字节序存储,因此就不需要再表示长度字段了。
还有些类型的数据,既没有长度的选项,也没有具体的值,比如null类型,只占用一个字节,表示类型,在其之后没有任何其他数据。
说完AMF,再回到我们的RTMP Body,RTMP Body就是按照AMF0规范,将数据包进行组织,然后再通过网络发送的。好了,接下来就结合wireshark实际抓到的RTMP数据包,一起熟悉AMF0,同时也熟悉RTMP Body的数据包组织方式。
2)_result的数据包
从抓包中可以看出,该RTMP Body中包含三种类型,4个AMF0编码的数据,一个String类型,一个Number类型,和2个Object类型。接下来我们一一分析下:
String类型
String类型对应的AMF0的type为0x02,表示string类型后面字符串的长度占用两个字节,在长度之后为具体的字符串,此处为"_result".
Number类型
Number类型对应的AMF0的type为0x00,number类型占用8个字节,不需要增加长度的表示,其后紧跟着的就是8个字节的数值表示(抓包显示文件为16进制)。
Object类型
object的类型为0x03,结束的时候会有一个object_end,类型是0x09,这也是AMF0中又一个只有类型,没有len和val的类型。图中绿色部分便是具体的object的数据。下面再深入内部看一眼:
通过抓包,我们可以看到,object类型的数据,内容实际上可以分为两部分,一部分表示object的名称,此处为fmsVer,另一部分就是具体的值。object的名称默认使用字符串,所以省略了类型,直接跟着的就是name对应的字符串的长度(占用两个字节表示),之后是具体的名称字符串;具体的值则再次按AMF0的格式进行编码,此处fmsVer的值为一个字符串,所以按照string类型的AMF0继续编码。
4.connect消息
当rtmp客户端和rtmp服务端握手完成之后,客户端就会向服务端发送connect消息。connect消息的格式按照RTMP Header+RTMP Body的格式组织。其中RTMP Header的Type ID为0x14,表示以AMF0编码的command消息。
对于RTMP Body,connect的组织格式如下:
connect消息由四部分组成,首先是command name,用字符串类表示命令的类型,即"connect";在其之后紧跟着的是事务id,该值永远设为1;再之后是connect消息中承载的所有object,用来标识一些参数;再后是可选的用户参数。一般比较少用。command object部分就是按照AMF0的标准表示了多个字段,主要包含app、flashVer、tcUrl、fpad、capabilities、audiocodecs、videocodecs等字段。
1)概览
所示是一条rtmp(connect消息),我们可以看到以0x03开头表示为Object数据,0x09表示Object的结尾,总共有app、flashVer、tcUrl、fpad、capabilities、audiocodecs、videocodecs、videoFunction这些字段。