AMF
AMF是Action Message Format的简写,它是一种二进制的数据格式, 它的设计,是为了把actionscript里面的数据(包括Object, Array, Boolean, Number等)序列化成 一段你基本看不大懂的二进制数据, 然后你可以把这段数据随意发送给其他地方的程序,比如发给远程的服务器, 在远程服务器那边, 又可以把这段数据给还原出来。以此达到一个数据传输的作用。
为什么要用AMF
通常情况下我们使用JSON或者XML来做数据的传输, 他们的好处是文本数据易读, 容易修改, 坏处在于文本数据体积较大,而且数据的组织有其局限性,比如,你如何在一个JSON/xml里面表达 内含自引用的数据?并且,本人自认为XML和JSON的解析效率并没有AMF高(。。请高人指正)
为什么要用AMF
二进制协议的缺点和优点刚好跟JSON/XML反过来。
那么, 二进制协议是不是只有AMF一个呢。。答案明显是否定的, 你完全可以自定义自己的二进制数据格式,用AMF只是由于它是现成的,拿来即可用,不用重新去发明轮子。当然,现在很多WEB游戏, 包括不少socialGame,都自己定义过一套二进制的数据格式。(比如人人网上的人人农场)
AMF消息流(AMF Message)
AMF消息流跟AMF不是一回事, AMF消息流就是一个数据包package,它里面包含了版本号,头部,消息体等数据, 头部和消息体里面用到的数据使用AMF的格式来进行存储。
AMF数据流常用于NetConnection, SharedObject等
AMF0和AMF3
从官方文档看,在2001年FLASH PLAYER6中就诞生了AMF0, 这是AMF的第一个版本, AMF0的设计比较简单, 直到FLASH8都没发生过什么变化。
后来AS3出世, 有了新的AVM, 于是也就重新设计了一套AMF3,它的基本目的就是 让数据进一步压缩再压缩,并且支持了一些新的数据类型比如bytearray等,具体有哪些改动大家自己去看PDF文档。
那么AMF3出来之后AMF0是不是可以退休了?答案也是否定的。lol
大家现在接触到的AMF3消息流基本上都是在AMF3外面包了一层AMF0, 也就是说我们看到的所有AMF数据流都是AMF0的,
当数据流中的某个数据的type=0×11时,才表示这个数据应该属于AMF3的数据, 在这个时候就会切换到AMF3的模式来处理这个数据。处理完之后当然还是继续回到AMF0的模式处理数据。
大家可以看看这段官方说明:
NOTE: With the introduction of AMF 3 in Flash Player 9, a special type marker was added to AMF 0 to signal a switch to AMF 3 serialization. This allows NetConnection requests to start out in AMF 0 and switch to AMF 3 on the first complex type to take advantage of the more the efficient encoding of AMF 3.
avmplus-object-marker=0×11
例子:一段AMF消息流
下面给大家看一个简单的例子,下面这个数据是去年我在玩某个国内游戏时截获的AMF数据流。
按照AMF0定义的协议,可以如下图来读懂这段数据:
00 03 表示版本号为3, 其实个人觉得这个版本号用处不大,只是可以提醒你数据流中可能会遇到AMF3的数据
00 00 表示头部的个数为0, 一般情况下貌似头部个数都是0,我还没理解什么情况下要用到头部
如果头部个数为n,那么接下来应该是n个头部的数据,这里因为n=0,所以直接跳过
00 01 表示消息体的个数为1
接下来就是(消息体的正文=targetURI+responseURI+内容长度+内容)* n,我们这个例子里n=1
targetURI是一个字符串,它表示这个消息要发到哪里去,在这个例子里它 = amfService.dispatchAMF
responseURI也是一个字符串,它其实就类似于一个token的作用,因为我们同时可能调用了很多个service,那么service返回回来的数据要回调哪个处理函数呢?
关键就在于这个responseURI,这个字符串会跟着数据流发出去, 然后还会回来。
AMF0规定了targetURI和responseURI都是一个UTF字符串, 也就是用2个字节来表示字符串长度,后面紧接着字符串正文。
继续往下看
00 16 表示targetURI的长度是0×16,
61 6D……41 4D 46 就是targetURI的值: amfService.dispatchAMF
00 03 表示responseURI的长度是3
2F 36 34 表示responseURI的值: /64
00 00 00 4D 表示后面的内容长度为0x4D
接下来的都是内容正文了,内容正文的数据全是AMF0的的数据
AMF的数据都是一个字节表示type(文档中称之为maker),然后紧接着数据
0A 表示这个是一个标准的Array
00 00 00 02 表示数组有2个元素
02 表示第一个元素的type是字符串,
00 30 表示第一个元素的字符串的长度是0×30
32 … 33 37 表示的是字符串的值
00 表示第2个元素是一个Number
40 00 00 00 00 00 00 00 表示的就是这个Number的值
上面这个例子正好不含有AMF3的数据,说明了开头的那个版本号3其实并没有太大意义,我们现在看到的数据都是AMF0的。
第2个例子:AMF3
下面再给大家看一个含有AMF3的例子:
我们直接切入正文段开始分析
00 00 00 1c 表示正文段长度是0x1c
11 表示数据类型是AMF3,下面切入AMF3的数据流分析
0A 表示下面是一个AMF3的object对象
0B 其二进制表示: 0000 1011 这里开始要用二进制来进行分析,秉承了ADOBE一向为求压缩而抠门到以位而非字节来作为最小存储单位的优良作风。
这个字节对整个AMF3的Object对象类型进行了关键性定义:
最右边一位是1: 表示这个object是一个真实的Object,而不是一个reference,(实际上第一次进行存储的Object都是一个真实的Object,只有第2次使用 的时候才是一个reference),
最右边第2位是一个1: 表示了描述这个object的trait数据正紧跟其后, 如果是0则也表示该trait数据是一个引用,请到引用缓存池里面进行寻找吧
最右边第3位是0: 表示了这个object并不是一个Externalizable的实例,简单来讲就是说这个object如果有私有成员,不好意思,这个是不能被序列化到 AMF数据里面的,除非您实现了IExternalizable这个接口。
最后边第4位是1: 表示这个object是一个dynamic对象,你可以随意往里面填充或者删除一些public的动态成员,也就是说紧接着后面我们需要把这个object里面含有的动态成员给parse出来
最左边4位 0000 ,表示了这个object的静态member个数是0
接着往下看,下面先紧跟着的是这个object的trait数据了,首先是ClassName,
01, 其二进制是 0000 0001 , 注意01并不是说长度为1,我们先看最右边一位是1,表示这个ClassName不是一个引用,不需要你到字符串的缓存池里面去寻找。最左边是7位,表示这个ClassName的长度正好是0, 就是空串”", 空串的ClassName就是指这个object的Class就是普通的Object类。
下面就是要读取一堆dynamic成员的key和value了,一开始并不知道有多少个成员,反正一直读,直到没有key为止。 key肯定是一个字符串,value类型未知。
0D 其二进制是0000 1101 , 最右边是1表示这不是一个reference,剩下的0000 110 表示这个key的长度是6, 然后后面就是
61 75 74 68 6F 72 表示的就是这个key的值: author
接着读value
06 表示value的类型是一个字符串
07 其二进制是000 0111, 最右边是1表示这不是一个reference,剩下的0000 11 表示这个key的长度是3, 然后后面就是
6C 64 78 表示value的值是ldx
后面的topic和AMF就不细说了, 然后大家看到最后一个字节是
01 其二进制是0000 0001 前面说deserializer并不知道有多少个key, 读到这里的时候还在试图读key,但是发现它不是一个字符串引用,同时它的长度是0000 000=0,那么表示没有Key可以读了, 结束
读取的结果是:
object :Object = { author: "ldx", topic: "AMF" };