十一 192012
 

最近做一个小东西,涉及到Flex中的WebService传输。完全使用Flex框架太过臃肿,想试一下能不能直接解析AMF。

AMF是Action Message Format协议的简称,AMF协议是Adobe公司自己的协议,主要用于数据交互和远程过程调用,在功能上相当于WebService,但是AMF与WebService中的XML不同的是AMF是二进制数据,而XML是文本数据,AMF的传输效率比XML高。AMF使用HTTP方式传输,目前主要是用于ActionScript中,即实现Flex和Server之间的通信。
AMF目前有两种版本,AMF0和AMF3,他们在数据类型的定义上有细微不同。关于AMF的官方文档参见这里

Type
Byte code
Notes

Number
0×00

Boolean
0×01

String
0×02

Object
0×03

MovieClip
0×04
Not available in Remoting

Null
0×05

Undefined
0×06

Reference
0×07

MixedArray
0×08

EndOfObject
0×09
See Object

Array
0x0a

Date
0x0b

LongString
0x0c

Unsupported
0x0d

Recordset
0x0e
Remoting, server-to-client only

XML
0x0f

TypedObject (Class instance)
0×10

AMF3 data
0×11
Sent by Flash player 9+

对应的枚举就是

public enum DataType
{
   Number = 0,
   Boolean = 1,
   String = 2,
   UntypedObject = 3,
   MovieClip = 4,
   Null = 5,
   Undefined = 6,
   ReferencedObject = 7,
   MixedArray = 8,
   End = 9,
   Array = 10,//0x0A
   Date = 11,//0x0B
   LongString = 12,//0x0C
   TypeAsObject = 13,//0x0D
   Recordset = 14,//0x0E
   Xml = 15,//0x0F
   TypedObject = 16,//0×10
   AMF3data=17//0×11
}

以上表列出了每种数据类型的表示方法,这样看并不容易理解,下面我就主要讲解一下常用的一些格式:
0.Number这里指的是double类型,数据用8字节表示,比如十六进制00 40 10 00 00 00 00 00 00就表示的是一个double数4.0,在C#中可以使用如下代码读取该数据:

byte[] d=new byte[]{0,0,0,0,0,0,0×10,0×40};//这里的顺序是和amf文件中的顺序正好相反,不要忘记了
double num=BitConverter.ToDouble(d,0);

1.Boolean对应的是.net中的bool类型,数据使用1字节表示,和C语言差不多,使用00表示false,使用01表示true。比如十六进制01 01就表示true。
2.String相当于.net中的string类型,String所占用的空间有1个类型标识字节和2个表示字符串UTF8长度的字节加上字符串UTF8格式的内容组成。比如十六进制03 00 08 73 68 61 6E 67 67 75 61表示的就是字符串,该字符串长8字节,字符串内容为73 68 61 6E 67 67 75 61,对应的就是“shanggua”。在C#中要读取字符串则使用:

byte[] buffer=new byte[]{0×73,0×68,0×61,0x6E,0×67,0×67,0×75,0×61};//03 00 08 73 68 61 6E 67 67 75 61
string str=System.Text.Encoding.UTF8.GetString(buffer);

3.Object在.net中对应的就是Hashtable,内容由UTF8字符串作为Key,其他AMF类型作为Value,该对象由3个字节:00 00 09来表示结束。C#中读取该对象使用如下方法:

private Hashtable ReadUntypedObject()
      {
         Hashtable hash = new Hashtable();
         string key = ReadShortString();
         for (byte type = ReadByte(); type != 9; type = ReadByte())
         {
            hash.Add(key, ReadData(type));
            key = ReadShortString();
         }
         return hash;
      }

4.Null就是空对象,该对象只占用一个字节,那就是Null对象标识0×05。
5. Undefined 也是只占用一个字节0×06。
6.MixedArray相当于Hashtable,与3不同的是该对象定义了Hashtable的大小。读取该对象的C#代码是:

private Hashtable ReadDictionary()
      {
         int size = ReadInt32();
         Hashtable hash = new Hashtable(size);
         string key = ReadShortString();
         for (byte type = ReadByte(); type != 9; type = ReadByte())
         {
            object value = ReadData(type);
            hash.Add(key, value);
            key = ReadShortString();
         }
         return hash;
      }

7.Array对应的就是.net中的ArrayList对象,该对象首先使用32位整数定义了ArralyList的长度,然后是密集的跟着ArrayList中的对象,读取该对象使用如下函数:

private ArrayList ReadArray()
      {
         int size = ReadInt32();
         ArrayList arr = new ArrayList(size);
         for (int i = 0; i < size; ++i)
         {
            arr.Add(ReadData(ReadByte()));
         }
         return arr;
      }

8.Date对应.net中的DateTime数据类型,Date在类型标识符0x0B后使用double来表示从1970/1/1到表示的时间所经过的毫秒数,然后再跟一个ushort的16位无符号整数表示时区。读取Date类型的C#代码为:  

private DateTime ReadDate()
     {
        double ms = ReadDouble();
        DateTime BaseDate = new DateTime(1970, 1, 1);
        DateTime date = BaseDate.AddMilliseconds(ms);       
        ReadUInt16(); //get’s the timezone       
        return date;
     }

9.LongString对应的也是string类型,不过和2对应的String不同的是这里使用32位整数来表示字符串的UTF8长度,而String使用的是16位。
10.XML是使用类型标识符0x0F后直接跟LongString类型的字符串表示。
这里大部分代码我都是摘自AMF.net 一个开源的.net AMF序列化和反序列化的库,大家若有兴趣可以到http://sourceforge.net/project/showfiles.php?group_id=159742 去下载。另外http://osflash.org/documentation/amf/astypes 这个英文网站也对AMF数据类型作了比较详细的介绍。

AMF文件总体来说分为4部分:前言(Preamble)、AMF头、AMF主体和主体的响应。
前言的前2字节用于说明AMF的版本,目前AMF有2个版本AMF0和AMF3.如使用AMF0则是:00 00
第3和第4字节用16位整数表示AMF头的数量。
每一个AMF头是由以下四部分组成:

引用

UTF string 表示Header的名字
Boolean 表示该Header是否是必须的
Int32表示Header的长度,但是好像很多情况下该值为FF FF FF FF,似乎这个字段没有意义。
Variable变量是某种AMF数据类型。

在Header表示完后,接下来是一个16位的整数用来表示AMF主体的数量,在这个数量之后才是AMF主体。
AMF主体主要由以下四部分组成:

引用

UTF String – Response表示请求的类和方法或响应的结果。
UTF String – Target是一个标识,其作用就是为了实现请求和响应的对应,通过Target找到该响应对应的请求。一般使用自增整数。
Int32- 表示主体的长度,该字段一般没有什么用
Variable变量表示主体的数据。

主体响应是客户端向服务器发送一个AMF请求以后服务器做出的和请求的主体格式相同的AMF响应,但是主体响应中的内容有所不同:
Response: 被设置为字符串‘null’.
Target: 是请求的Target值再加上“/onStatus”, “onResult”, 或者 “/onDebugEvents”组成. “/onStatus” 是为运行时错误而准备的我们一般不关心这个. “/onResult” 表示该请求被正确调用. “/onDebugEvents” 是在调试时使用的,这里也不用关心. 如果请求的Target是‘/1’, 那么被成功调用以后的主体响应应该是: ‘/1/onResult’ 。
Data:就是响应后返回的AMF对象。
说了这么多估计还是感觉比较抽象,下面给出个实例:
AMF 16进制内容

00000000h: 00 00 00 00 00 01 00 1B 7A 68 2E 66 6C 65 65 74 ; ……..zh.fleet
00000010h: 53 65 72 76 69 63 65 2E 67 65 74 46 6C 65 65 74 ; Service.getFleet
00000020h: 52 6F 77 00 03 2F 37 39 00 00 00 13 0A 00 00 00 ; Row../79……..
00000030h: 03 02 00 01 35 02 00 03 38 34 35 02 00 01 35      ; ….5…845…5

以上是客户端向服务器发送的一个AMF请求。我们可以按照前面说的封装方式将该amf解析如下:
00 00(AMF0版本)00 00(Header个数为0)00 01(AMF主体有1个)
00 1B(请求的方法的字符串长度为27个字节)
7A ……77(这27个直接就是调用的类和方法:“zh.fleetService.getFleetRow”)
00 03(请求的Target字符串长3字节) 2F 37 39(Target的内容:“/79”)
00 00 00 13(主体的长度为19)
0A(传入的变量是一个Array)00 00 00 03(该Array的长度为3)02 00 01 35(Array的第一个值是字符串“5”)02 00 03 38 34 35(Array的第二个值是字符串“845”)02 00 01 35(Array的第三个值是字符串“5”)
现在整个AMF对象都解析出来了,我们可以认为是客户端调用了服务器的方法:zh.fleetService.getFleetRow(“5″, “845″, “5″)
服务器返回的AMF文件的内容的解析方式相同,这里我就不再重复了。
现在我们已经对AMF文件有了一个清晰的认识了。那么接下来就是要抓包,看某些在Flex上的操作对应的发送了什么AMF文件,服务器返回了什么AMF文件。将这些AMF文件解析出来然后就可以看到调用了API了。