流媒体学习2

发布时间 2023-12-08 14:01:58作者: 泽良_小涛

四、RTMP详解

RTMP是Real Time Messaging Protocol(实时消息传输协议)的首字母缩写。该协议是应用层协议,基于TCP。RTMP是一种设计用来进行实时通信的网络协议,主要用来在Flash平台和支持RTMP协议的流媒体/交互服务器之间进行以视频和数据通信。直播场景中使用RTMP协议比较多。

1.握手

RTMP协议是基于TCP的,TCP建立连接有三次握手。如下图:

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EVFMyNHVoMnJzaWJubjQ4cXhJYXIwSnNWQ3llU1l1dmxaQkZvU3ptNTlVRGdFWENNZDhITFprRFdDdmZkOHBQNEFybFYxdjlzZzg2ZlEvNjQw?x-oss-process=image/format,png

所以,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

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EVFMyNHVoMnJzaWJubjQ4cXhJYXIwSnNyZVJ6UDRQdzE4QkY5c0pkeFdDQ2lhSWxyYUhDTUFoRGdTZFh0Zm1raDNaRjBVTmlhTVNyMVA4US82NDA?x-oss-process=image/format,png

C0和S0数据包占用一个字节,表示RTMP版本号。目前RTMP版本定义为3,0-2是早期的专利产品所使用的值,现已经废弃,4-31是预留值,32-255是禁用值。

C1和S1https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EVFMyNHVoMnJzaWJubjQ4cXhJYXIwSnNubU5LV3VBMTZnWnJPd0lJZ3JpY2lhNGFPVjE3WmwxekEyckFMVnNMcFRjZTFpYVlMTTJsSExCa2cvNjQw?x-oss-process=image/format,png

C1和S1数据包占用1536个字节。包含4个字节的时间戳,4个字节的0和1528个字节的随机数。

C2和S2https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EVFMyNHVoMnJzaWJubjQ4cXhJYXIwSnN6eUk4c0FCV2g0NUhGUm50OEY1NkVWa0V1SHJ4cUoySHlKaEI4bEdGMVVqMWZ4blNNME9wbGcvNjQw?x-oss-process=image/format,png C2和S2数据包占用1536个字节,包含4个字节的时间戳,4个字节的对端的时间戳(C2数据包为S1数据包的时间戳,S2为C1数据包的时间戳)。

3)几种握手的状态

未初始化

协议版本被发送,客户端服务端都是未初始化的,客户端在C0数据包中发送协议版本,如果服务器支持这个版本,将会发送S0和S1作为响应,如果不支持,则服务端会用响应的动作来响应,在RTMP中这个动作是结束这个连接。

版本发送完成

客户端和服务端在未初始化状态之后都进入到版本发送完成状态,客户端等待包S1,服务端等待包C1,在收到相应的包后,客户端发送包C2,服务端发送包S2,状态变成询问发送完成。

询问发送完成

客户端和服务端等待S2和C2。

握手完成

客户端和服务端开始交换信息。

抓包

注意,wireshart过滤RTMP协议的条件是rtmpt,不要忘了后面的t。

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EVFMyNHVoMnJzaWJubjQ4cXhJYXIwSnNZa3ZoWGxleWliVFFDRVBxdHB6REdPRGRHcElIc2pPYmNFSnpaMmU5SkdGM21yWVFRSGFrRlFBLzY0MA?x-oss-process=image/format,png

4)抓包实战

  • source为客户端(192.17.1.92),握手时首先向server(192.17.1.200)发送C0和C1数据包;
  • 服务端收到C0和C1数据包之后,直接发送S0+S1+S2数据包;
  • 客户端收到服务端发来的S2数据包后,发送C2数据包,至此,rtmp握手完成,可以进行数据交换了。

C0和C1数据包

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EVFMyNHVoMnJzaWJubjQ4cXhJYXIwSnN6TVgzNkJSa041VmZJTzJETUN1RDlBUTF4SkNLZ0gxaWEyQVlsZTJKM3FleG5WNDQ0UWljVjdKQS82NDA?x-oss-process=image/format,png

S0+S1+S2的数据

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EVFMyNHVoMnJzaWJubjQ4cXhJYXIwSnN2YWlhSWQySmNQQlZwQ3l1SHpPdWR4QzZIQWlhQ29haWFreEJpYkp6YlNFaDNlcnVRZGNWRHhjWVh3LzY0MA?x-oss-process=image/format,png

C2的数据

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EVFMyNHVoMnJzaWJubjQ4cXhJYXIwSnNEYk43MmlhMGZFQmNQandCRjVpYWVoY0duUHJkQWtVWnNZN01BTHdUdDhwS2NzcHNxcHZIQk1Ydy82NDA?x-oss-process=image/format,png

握手的过程主要完成了两个工作,一是对rtmp的版本进行校验,二是发送了一些随机数据,用于网络状况的检测。握手成功之后,表示客户端和服务器之间可以正常进行网络通信,接下来就可以进行数据的交互了。

2.rtmp Header

Rtmp协议握手完成之后,就可以进行数据交互了,但交换的数据格式需要一个组织的标准,发送端按照该标准进行数据的组装,接收方按照该标准进行数据的拆解,这样才能完成通信。rtmp的协议的数据包,总的来讲分为两大部分,一部分是Rtmp Header,另一部分为Rtmp Body。

1)RTMP header数据包的长度

RTMP header的长度不固定,可能的长度为12字节,8字节,4字节,1字节。具体长度为多少个字节,由RTMP header数据包的第一个字节的高2位决定。

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EUkVHaFRhaWJoSVBsMWlheXNoblJJNkVodGV1YVRRNDRoYVBpYVJoa0xhUDhzRzJ5TlJuQTIyYnBvZWd1QzM5VVlHN2xoaWNzZ0FXMXoyRlEvNjQw?x-oss-process=image/format,png

如下方表格,Format决定了RTMP header的长度为多少个字节:

下面,通过wireshark抓包看下,RTMP HEADER的长度。

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EUkVHaFRhaWJoSVBsMWlheXNoblJJNkVobE83UDhmNXA4RFNOYlB4S2NkNldybG5UUXdWT0NSSTFldjRERnV2Mk1VcXZweGg0NGljcXlEdy82NDA?x-oss-process=image/format,png

图中,RTMP Header的第一个字节为0x03,高两位的值为00,所以,整个RTMP Header的长度就是12个字节了。

2) Chunk Stream ID

第一个字节的低6位,命名为Chunk Stream ID,Chunk Stream ID用来表示消息的级别:

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EUkVHaFRhaWJoSVBsMWlheXNoblJJNkVoaWN3dUUzc05QV0lLVVd6dzdKdnNDNzNxYXRhMEdncENpYXZaTVVYUGVkT0NHRHJibWlheXg3ckJBLzY0MA?x-oss-process=image/format,png

chunk_stream id为3,表示消息的级别为high levle,这一条消息实际上是一条connect的消息。

知道了RTMP header的第一个字节的作用以后,接下来我们看下几种不同长度的RTMP Header。

3)12字节的RTMP Header

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EUkVHaFRhaWJoSVBsMWlheXNoblJJNkVoTEhsYjI5dmNDZXU1aWJZTkZHU2lia1JRTWliaWNLV2JUNWZmd1BpYlE0QW0xZFp2Rkdxb2EyWmRRaEEvNjQw?x-oss-process=image/format,png

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EUkVHaFRhaWJoSVBsMWlheXNoblJJNkVoaHlCSk0wamdHV25maklHaGFVdGppYmlhMVNtZjFnUE1heGxOMkN5YnZGclp5ZG9QaktENW9DeVEvNjQw?x-oss-process=image/format,png

  • 第一个时间戳三个字节。
  • 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 

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EUkVHaFRhaWJoSVBsMWlheXNoblJJNkVoQVRDNHFCd0FVNFpBcW1XYVFBZXdrSGlhZm5FUnZ4d2Q5cjlpY01wTTRGTzhIMENPQ2Q4czU4Q1EvNjQw?x-oss-process=image/format,png

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EUkVHaFRhaWJoSVBsMWlheXNoblJJNkVoMEdua01wWFpHRUtFc0xuU3htTEFOYUVlMjlCaGVBQmZLTmliQ1hjUTJHWnNpYnVtTFRVeWZFU1EvNjQw?x-oss-process=image/format,png

  • 第一个字节,高2位为01,所以RTMP Header的长度为8字节
  • 接下来是时间戳的delta,简单讲就是时间戳的变化量
  • BodySize不多说
  • TypeID此处为0x04,也就是用户控制信息(本条消息实际上是rtmp客户端和服务器之间的Ping Response)。

5)4字节的RTMP Header

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EUkVHaFRhaWJoSVBsMWlheXNoblJJNkVoVHRUV1Y4WHNpYzRsNkNYWGljTzhiSjI5Nkd0NjVzbmRiOHpJUUhBNGJpY1V1T2lhY1h3dExtWktLQS82NDA?x-oss-process=image/format,png

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EUkVHaFRhaWJoSVBsMWlheXNoblJJNkVoSHA5anVDcGlja1ZVSXZvVks1T1NrajVYa1ExaWJtVDdlYjVPTnpmZ3lpYWc0N05EaWE4a0htcG1IZy82NDA?x-oss-process=image/format,png

4字节的就比较简单了,除了第一个字节之外,只有一个关于时间戳的增量,占用3个字节。可以看到,第一个字节为0xa2,所以高2位的值为10,所以,RTMP Header占用4个字节,后面跟着的时间戳的增量。

6)1字节的RTMP Header

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EUkVHaFRhaWJoSVBsMWlheXNoblJJNkVoWE1vcnQwUnUxdHVuM2NSb280amJTdHI4d2hRWHAwY0s0SUNlUFM2WmlhYkFZNzFOVXNzOXNKZy82NDA?x-oss-process=image/format,png

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EUkVHaFRhaWJoSVBsMWlheXNoblJJNkVoaWN5c0dhNUdwOHBxeGRwYTZ6N2hZcDJtQmozakxHaWJoZlFvTll1SzhITXg2WllCMHlLQ1VhclEvNjQw?x-oss-process=image/format,png

一个字节,就是第一个字节,后面啥也没有喽,高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类型,只占用一个字节,表示类型,在其之后没有任何其他数据。

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EU3lYTWd3NDlheE42dWpXTE1nM3l3a3BEWmVpYlNObFV6T2JJd2xoemNoR2hKQm1VTjhvOG1PeVVSOTN4Nmx1TVBlTmtLajNRdURMZHcvNjQw?x-oss-process=image/format,png

说完AMF,再回到我们的RTMP Body,RTMP Body就是按照AMF0规范,将数据包进行组织,然后再通过网络发送的。好了,接下来就结合wireshark实际抓到的RTMP数据包,一起熟悉AMF0,同时也熟悉RTMP Body的数据包组织方式。

2)_result的数据包

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EU3lYTWd3NDlheE42dWpXTE1nM3l3a1NlN1RFUDd4V0FQTnJpYlJyM1ZmRUc3dEZBazdIWXc0bDdicFdqcWlhMmFidHc5Yjkyejg4TUJBLzY0MA?x-oss-process=image/format,png

从抓包中可以看出,该RTMP Body中包含三种类型,4个AMF0编码的数据,一个String类型,一个Number类型,和2个Object类型。接下来我们一一分析下:

String类型

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EU3lYTWd3NDlheE42dWpXTE1nM3l3a1JMUGtDODJ3cEdSeHc1MGhld2RwMTY4OXRpY05pYkVaaEkzaWIyUkhmVGFRdG9GRGljZEs0SGljTEZnLzY0MA?x-oss-process=image/format,png

String类型对应的AMF0的type为0x02,表示string类型后面字符串的长度占用两个字节,在长度之后为具体的字符串,此处为"_result".

Number类型

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EU3lYTWd3NDlheE42dWpXTE1nM3l3a05lZll3a0gySE4wTU5iVUJERWhSeXdobGtKYk5NWkJ5SnhkM1JHNjRJempWYUhEZ283bExUdy82NDA?x-oss-process=image/format,png

Number类型对应的AMF0的type为0x00,number类型占用8个字节,不需要增加长度的表示,其后紧跟着的就是8个字节的数值表示(抓包显示文件为16进制)。

Object类型

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EU3lYTWd3NDlheE42dWpXTE1nM3l3a3JPZHdGTUlqdHo1VTNYYzVJTkNsUDhGMHNpYVBxdmVGOEtmenNENzFaeVl4OWROUUlhMWZLbEEvNjQw?x-oss-process=image/format,png

object的类型为0x03,结束的时候会有一个object_end,类型是0x09,这也是AMF0中又一个只有类型,没有len和val的类型。图中绿色部分便是具体的object的数据。下面再深入内部看一眼:

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EU3lYTWd3NDlheE42dWpXTE1nM3l3a3dNZlJBeWFpY3VQNVF5UVZ0c21TcTQyRGhsTUxybFRtMDU5YXJjc290dVF2NkNmTndLaWJTaDdRLzY0MA?x-oss-process=image/format,png

通过抓包,我们可以看到,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的组织格式如下:

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EUW5pYklEZWxwUFVmaWJwV3BuSkxMeGliTGhlZXJoRHh2MmthM1FWejlwOVdpY1ZoWE1XczBMUWxLQW1CdkpGNkducExVMlZjUHpvaWM4QjFRLzY0MA?x-oss-process=image/format,png

connect消息由四部分组成,首先是command name,用字符串类表示命令的类型,即"connect";在其之后紧跟着的是事务id,该值永远设为1;再之后是connect消息中承载的所有object,用来标识一些参数;再后是可选的用户参数。一般比较少用。command object部分就是按照AMF0的标准表示了多个字段,主要包含app、flashVer、tcUrl、fpad、capabilities、audiocodecs、videocodecs等字段。

1)概览

https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9rYVk0SkcxdE9EUW5pYklEZWxwUFVmaWJwV3BuSkxMeGliTEZFc1ZCU1l2MWljdTFORFptYk1qZEpncW93cXpWWGljOWlhQjBkeHh6V0ZMZ0NBUEx1ZU9yWlNvZy82NDA?x-oss-process=image/format,png

所示是一条rtmp(connect消息),我们可以看到以0x03开头表示为Object数据,0x09表示Object的结尾,总共有app、flashVer、tcUrl、fpad、capabilities、audiocodecs、videocodecs、videoFunction这些字段。