以下是本人对rtmp协议的分析和理解,前阶段我在上网时看到有一位高手对rtmp协议部分进行详细剖析,此时本人正在做flex和java进行通讯的一个项目,其协议就用到了rtmp,经过我近段时间来对red5源代码的分析研究,觉得之前有些地方可能不太完善,还得进一步细化。
第一步:建立rtmp协议
要想建立rtmp协议,则在服务器必须要进行相应数据验证,flash或flex(创建NetConnection连接过程中)客户端需要向服务器端发送13
RTMP协议概述
介绍:
RTMP协议是被Flash用于对象,视频,音频的传输.该协议建立在TCP协议或者轮询HTTP协议之上.
RTMP协议就像一个用来装数据包的容器,这些数据可以是AMF格式的数据,也可以是FLV中的视/音频数据.
一个单一的连接可以通过不同的通道传输多路网络流.这些通道中的包都是按照固定大小的包传输的.
var nc:NetConnection = new NetConnection();
var connected:Boolean = nc.connect("rtmp:/localhost/roomapp")
握手
Client → Server :向服务器发出握手请求.这不属于协议包一部分,该握手请求第一个字节为(0×03),其后跟着1536个字节.
Server → Client :服务器向客户端回应握手请求.这部分的数据仍然不属于RTMP协议的部分.该回应的其实字节仍然为(0x03),但是后边跟着个长度为1536个字节 (一共为3072 )的包块.第一个1536块看上去似乎可以是任意内容,甚至好像可以是Null都没有关系.第二个1536的代码块,是上一步客户端向服务器端发送的握手 请求的内容.
Client→Server:把上一步服务器向客户端回应的第二块1536个字节的数据块.
至此客户端与服务器端的握手结束,下面将发送RTMP协议的包内容.
Client → Server :向服务器发送连接包.
Server → Client :服务器回应.
... .... 等等... ...
引用如下(红色背景为我更正信息):
RTMP协议封包 由一个包头和一个包体组成,包头可以是4种长度的任意一种:12, 8, 4, 1 byte(s).完整的RTMP包头应该是12bytes,包含了时间戳,AMFSize,AMFType,StreamID信息, 8字节的包头只纪录 了时间戳,AMFSize,AMFType,其他字节的包头纪录信息依次类推 。包体最大长度默认为128字节,通过chunkSize可改变包体最大长 度,通常当一段AFM数据超过128字节后,超过128的部分就放到了其他的RTMP封包中,包头为一个字节.
完整的12字节RTMP包头每个字节的含义:
用途 | 大小(Byte) | 含义 |
Head_Type | 1 | 包头 |
TiMMER | 3 | 时间戳 |
AMFSize | 3 | 数据大小 |
AMFType | 1 | 数据类型 |
StreamID | 4 | 流ID |
一、Head_Type
第一个字节Head_Type的前两个Bit决定了包头的长度.它可以用掩码0xC0进行"与"计算:
Head_Type的前两个Bit和长度对应关系:
Bits | Header Length | ||||||||||||||||||||||||||||||||||||||||||||||||
00 | 12 bytes | ||||||||||||||||||||||||||||||||||||||||||||||||
01 | 8 bytes | ||||||||||||||||||||||||||||||||||||||||||||||||
10 | 4 bytes | ||||||||||||||||||||||||||||||||||||||||||||||||
11 | 1 byte | ||||||||||||||||||||||||||||||||||||||||||||||||
三、AMFSize 四、AMFType
| |||||||||||||||||||||||||||||||||||||||||||||||||
red5代码分析如下:
Head_Type
我看到代码red5与以上分析有些出入.
“Head_Type的前两个Bit和长度对应关系”分析没有错.
而red5代码中计算头字节公式如下(头字节不一定是一个字节,有可能是两个字节或三个字节,需要说明的头字节计算结果最终会影响ChannelId):
1.当Head_Byte & 0x3F=0时,headerValue= ((int) headerByte & 0xff) << 8| ((int) in.get() & 0xff)
此时读出字节头为两个字节
2.当Head_Byte & 0x3F=1时,headerValue = ((int) headerByte & 0xff) << 16
| ((int) in.get() & 0xff) << 8 | ((int) in.get() & 0xff)
此时读出字节头为三个字节
3.如果以上两个条件都不满足,则headerValue = (int) headerByte & 0xff;
channelId计算公式为:
1. 如果头为一个字节,则channelId = (headerValue & 0x3f)
2. 如果头为两个字节,则channelId = 64 + (headerValue & 0xff)
3. 如果头为三个字节,则channelId = 64 + ((headerValue >> 8) & 0xff) + ((headerValue & 0xff) << 8)
Head_Type的后面6个Bit和StreamID决定了ChannelID。 StreamID和ChannelID对应关系:StreamID=(ChannelID-4)/5+1 参考red5
ChannelID | Use |
02 | Ping和ByteRead通道 |
03 | Invoke通道 我们的connect() publish()和自字写的NetConnection.Call() 数据都是在这个通道的 |
04 | Audio和Vidio通道 |
05 06 07 | 服务器保留,经观察FMS2用这些Channel也用来发送音频或视频数据 |
以下是用一个截包工具获取原始二进制数据。
03 00 00 00 00 01 30 14 00 00 00 00 02 00 07 63 6F 6E 6E 65 63 74 00 3F F0 00 00 00 00 00 00 03 00 03 61 70 70 02 00 08 72 6F 6F 6D 2F 30 30 31 00 08 66 6C 61 73 68 56 65 72 02 00 0E 57 49 4E 20 31 30 2C 30 2C 31 32 2C 33 36 00 06 73 77 66 55 72 6C 06 00 05 74 63 55 72 6C 02 00 1C 72 74 6D 70 3A 2F 2F 31 39 32 2E 31 36 38 2E 31 2E 31 38 2F 72 6F 6F 6D 2F 30 30 31 00 04 66 70 61 64 01 00 00 0C 63 61 70 61 62 69 6C 69C3 74 69 65 73 00 40 2E 00 00 00 00 00 00 00 0B 61 75 64 69 6F 43 6F 64 65 63 73 00 40 A8 EE 00 00 00 00 00 00 0B 76 69 64 65 6F 43 6F 64 65 63 73 00 40 6F 80 00 00 00 00 00 00 0D 76 69 64 65 6F 46 75 6E 63 74 69 6F 6E 00 3F F0 00 00 00 00 00 00 00 07 70 61 67 65 55 72 6C 06 00 0E 6F 62 6A 65 63 74 45 6E 63 6F 64 69 6E 67 00 40 08 00 00 00 00 00 00 00 00 09 02 00 0F 30 38 31 32 31 31C3 30 39 32 30 32 32 32 32 32 02 00 02 33 34 02 00 0D 31 39 35 2E 31 36 38 2E 31 34 2E 32 32 02 00 03 30 30 31 02 00 01 30 02 00 01 38 02 00 01 30
1. 下面我们来对鲜绿色背景头数据进行分析
从上面剖析包头说明判断可以第一个字节0x03,此包头有12个字节.
Head_Type=03
TiMMER=00 00 00
AMFSize=00 01 30 表示经过组合之后AMF的总字节长度(默认情况下每个RTMP包为128字节,由C3字节得知,以上包有3个rtmp封包)
AMFType=14 表示接下来第一个字符串为远程调用方法名称
02 00 07 63 6F 6E 6E 65 63 74 02字节表示字符串类型 00 07表示该字符串长度为7个字节63 6F 6E 6E 65 63 74为connect远程方法
StreamID=00 00 00
以后我还会陆续讲解rtmp协议中如何传输数组,map,List,Bean对象,以及基本数据类型