转载自:http://nkwavelet.blog.163.com/blog/static/227756038201412031720398/
AMF协议是Action Message Format协议的简称,AMF协议是Adobe公司的协议,主要用于数据交互和远程过程调用,在功能上与WebService相当,但AMF与WebService中的xml不同在于AMF是二进制数据,而xml是文本数据,AMF的传输效率比xml高。AMF使用http方式传输,目前主要用于ActionScript中,实现Flex与Service之间的通信。AMF目前有两种版本,AMF0和AMF3,他们在数据类型的定义上有细微不同。
AMF最大的特色在于可直接将Flash内置对象,例如Object, Array, Date, XML,传回服务器端,并且在服务器端自动进行解析成适当的对象,这就减轻了开发人员繁复工作,同时也更省了开发时间。由于AMF采用二进制编码,这种方式可以高度压缩数据,因此非常适合用来传递大量的资料。数据量越大,Flash Remoting的传输效能就越高,远远超过Web Service。至于XML, 它们使用纯文本的传输方式,效能就更不能与Flash Remoting相提并论了。除了AMF编码进行高效数据操作的功能之外,ByteArray还有一个很酷的功能,就是从内存中深层次的Copy(Clone)整个对象。
具体AMF是怎么使用的在这里就不做详细讨论了。amf.c是RTMPDump解析RTMP协议的函数存放的地方,在这里贴上其源代码。对其中大部分代码作了比较详细的注释。建议看代码之前熟悉AMF协议,手头上准备好AMF0和AMF3的官方协议文档。
// 大端Big-Endian :
//低地址存放最高有效位(MSB),既高位字节排放在内存的低地址端,
//低位字节排放在内存的高地址端。 符合人脑逻辑,与计算机逻辑不同。
//
// 网络字节序 Network Order :
//TCP/IP各层协议将字节序定义为Big-Endian,因此TCP/IP协议中使用的
//字节序通常称之为网络字节序。
//
// 主机序 Host Order :
//它遵循Little-Endian规则。所以当两台主机之间要通过TCP/IP协议进行
//通信的时候就需要调用相应的函数进行主机序(Little-Endian)和网络
//序(Big-Endian)的转换。
//
// AMF数据采用 Big-Endian(大端模式),主机采用Little-Endian(小端模式)
/**
* @brief 将以data为起始地址的16位数据转化为无符号短整型值
*/
unsigned short AMF_DecodeInt16(const char *data);
/**
* @brief 将以data为起始地址的24位数据转化为无符号整型值
*/
unsigned int AMF_DecodeInt24(const char *data);
/**
* @brief 将以data为起始地址的32位数据转化为无符号整型值
*/
unsigned int AMF_DecodeInt32(const char *data);
/**
* @brief 将以data为起始地址的字符串数据解析出来,保存在bv中.
*
* data中的数据格式为: | 字符串长度(2字节) | 实际字符串 |
*
* 其中 bv->av_len 表示字符串的长度,占2个字节
* bv->av_val 缓冲区用于保存实际的字符串。
*/
void AMF_DecodeString(const char *data, AVal *bv);
/**
* @brief 将以data为起始地址的长字符串数据解析出来,保存在bv中.
*
* data中的数据格式为: | 字符串长度(4字节) | 实际字符串 |
*
* 其中 bv->av_len 表示字符串的长度,占4个字节
* bv->av_val 缓冲区用于保存实际的字符串。
* 长字符串的长度可能超过16位无符号整型表示范围,因此采用32位.
*/
void AMF_DecodeLongString(const char *data, AVal *bv);
/**
* @brief 将以data为起始地址的8个字节数据转化double值
*/
double AMF_DecodeNumber(const char *data);
/**
* @brief 将以data为起始地址的1个字节数据转化boolean值
*/
int AMF_DecodeBoolean(const char *data);
/**
* @brief 将短整型值转化为2个字节保存到缓冲区output中.
*
* @param output : 输出缓冲区用于保存编码值
* @param outend : 缓冲区末端地址
* @param nVal : 待编码的短整型值
*
* @return 编码成功的话,output向后移动2个字节,否则返回NULL.
*/
char *AMF_EncodeInt16(char *output, char *outend, short nVal);
/**
* @brief 将整型值转化为3个字节保存到缓冲区output中.
*
* @param output : 输出缓冲区用于保存编码值
* @param outend : 缓冲区末端地址
* @param nVal : 待编码的整型值
*
* @return 编码成功的话,output向后移动3个字节,否则返回NULL.
*/
char *AMF_EncodeInt24(char *output, char *outend, int nVal);
/**
* @brief 将整型值转化为4个字节保存到缓冲区output中.
*
* @param output : 输出缓冲区用于保存编码值
* @param outend : 缓冲区末端地址
* @param nVal : 待编码的整型值
*
* @return 编码成功的话,output向后移动4个字节,否则返回NULL.
*/
char *AMF_EncodeInt32(char *output, char *outend, int nVal);
/**
* @brief 将bv中的字符串编码到缓冲区output中. 编码的格式为:
* | 类型(1字节) | 长度(2或4字节) | 实际的字符串 |
*
* @param output : 缓冲区用于保存编码值
* @param outend : 缓冲区末端地址
* @param bv : 结构体保存有字符串数据和长度
*
* @return 编码成功返回编码后新的output地址值,否则返回NULL.
*/
char *AMF_EncodeString(char *output, char *outend, const AVal *bv)
{
// 判断缓冲区空间是否足够大
if ((bv->av_len < 65536 && output + 1 + 2 + bv->av_len > outend) ||
output + 1 + 4 + bv->av_len > outend)
return NULL;
// 短字符串
if (bv->av_len < 65536)
{
*output++ = AMF_STRING;
output = AMF_EncodeInt16(output, outend, bv->av_len);
}
else// 长字符串
{
*output++ = AMF_LONG_STRING;
output = AMF_EncodeInt32(output, outend, bv->av_len);
}
// 将bv中的字符串数据拷贝到output中,并修改指针output值
memcpy(output, bv->av_val, bv->av_len); output += bv->av_len; return output;
}
/**
* @brief 将double值编码到缓冲区output中. 编码的格式为:
* | 类型(1字节) | double值(8字节) |
*
* @param output : 输出缓冲区用于保存编码值
* @param outend : 缓冲区末端地址
* @param dVal : 待编码的double值
*
* @return 编码成功返回编码后新的output地址值,否则返回NULL.
*/
char *AMF_EncodeNumber(char *output, char *outend, double dVal);
/**
* @brief 将boolean值编码到缓冲区output中. 编码的格式为:
* | 类型(1字节) | 0或1(1字节) |
*
* @param output : 输出缓冲区用于保存编码值
* @param outend : 缓冲区末端地址
* @param bVal : 待编码的boolean值
*
* @return 编码成功返回编码后新的output地址值,否则返回NULL.
*/
char *AMF_EncodeBoolean(char *output, char *outend, int bVal);
/**
* @brief 将(strName, strValue)组编码到缓冲区output中. 编码格式:
*
* | name长度(2字节) | name实际字符串 | 类型(1字节) | value长度(2或4字节) | value字符串 |
*
* 举例: strName = {"ip", 2}, strValue = {"192.168.19.201", 14}
* 其中strName中的字符串长度一定小于65536,而strValue中的字符串长度不定.
*
* @param output : 缓冲区用于保存编码值
* @param outend : 缓冲区末端地址
* @param strName : 某个变量的名字,用字符串表示
* @param strValue : 上述变量的值,也用字符串表示
*
* @return 编码成功返回编码后新的output地址值,否则返回NULL.
*/
char *AMF_EncodeNamedString(char *output, char *outend, const AVal *strName, const AVal *strValue);
/**
* @brief 将(strName, dVal)组编码到缓冲区output中. 编码格式:
*
* | name长度(2字节) | name实际字符串 | 类型(1字节) | double值(8字节) |
*
* 举例: strName = {"width", 5}, dVal = 640.
* 其中strName中的字符串长度一定小于65536.
*
* @param output : 缓冲区用于保存编码值
* @param outend : 缓冲区末端地址
* @param strName : 某个变量的名字,类型为字符串
* @param dVal : 上述变量的值,类型为double
*
* @return 编码成功返回编码后新的output地址值,否则返回NULL.
*/
char *AMF_EncodeNamedNumber(char *output, char *outend, const AVal *strName, double dVal);
/**
* @brief 将(strName, bVal)组编码到缓冲区output中. 编码格式:
*
* | name长度(2字节) | name实际字符串 | 类型(1字节) | bVal值(1字节) |
*
* 举例: strName = {"stereo", 6}, bVal = 1.
* 其中strName中的字符串长度一定小于65536.
*
* @param output : 缓冲区用于保存编码值
* @param outend : 缓冲区末端地址
* @param strName : 某个变量的名字,类型为字符串
* @param bVal : 上述变量的值,类型为boolean
*
* @return 编码成功返回编码后新的output地址值,否则返回NULL.
*/
char *AMF_EncodeNamedBoolean(char *output, char *outend, const AVal *strName, int bVal);
/**
* @brief 获取amf对象属性的名字
*/
void AMFProp_GetName(AMFObjectProperty *prop, AVal *name);
/**
* @brief 设置amf对象属性的名字
*/
void AMFProp_SetName(AMFObjectProperty *prop, AVal *name);
/**
* @brief 获取amf对象属性的数据类型
*/
AMFDataType AMFProp_GetType(AMFObjectProperty *prop);
/**
* @brief 获取amf对象属性的double值
*/
double AMFProp_GetNumber(AMFObjectProperty *prop);
/**
* @brief 获取amf对象属性的boolean值
*/
int AMFProp_GetBoolean(AMFObjectProperty *prop);
/**
* @brief 获取amf对象属性的AVal值,即字符串本身及其长度
*/
void AMFProp_GetString(AMFObjectProperty *prop, AVal *str);
/**
* @brief 获取amf对象属性所属的amf对象
*/
void AMFProp_GetObject(AMFObjectProperty *prop, AMFObject *obj);
/**
* @brief 判断amf对象属性的类型是否有效
*/
int AMFProp_IsValid(AMFObjectProperty *prop);
/**
* @brief 编码一个amf对象属性,包括属性名及其值
*/
char *AMFProp_Encode(AMFObjectProperty *prop, char *pBuffer, char *pBufEnd)
{
// 判断属性类型是否有效
if (prop->p_type == AMF_INVALID)
return NULL;
// 判断缓冲区空间是否足够大
if (prop->p_type != AMF_NULL && pBuffer + prop->p_name.av_len + 2 + 1 >= pBufEnd)
return NULL;
// 编码属性名字,格式为: | 长度(2字节) | 名字字符串 |
if (prop->p_type != AMF_NULL && prop->p_name.av_len)
{
*pBuffer++ = prop->p_name.av_len >> 8;
*pBuffer++ = prop->p_name.av_len & 0xff;
memcpy(pBuffer, prop->p_name.av_val, prop->p_name.av_len);
pBuffer += prop->p_name.av_len;
}
// 根据属性值的类型选择相应的编码方式
switch (prop->p_type)
{
... ... (省略)
}
return pBuffer;
}
#define AMF3_INTEGER_MAX268435455// 0x000000000FFFFFFF
#define AMF3_INTEGER_MIN -268435456// 0xFFFFFFFFF0000000
/**
* @brief AMF3使用可变长度的无符号29位整数编码。对于一个正常的32-bit的整数,
* 需要4个字节来存储,然而前3个字节的最高位是用于标识下一个字节是不是整数的一部分。
* 该编码规则可参考AMF3官方协议1.3.1节
*
* 例如:
* 0x7A : 表示一个7-bit整数,因为首位是0,后面的字节就不是该整数的一部分.
* 该编码表示的整数值为 0x7A
* 0x8A7B : 表示一个14-bit整数,因为第2个字节的首位为0,之后字节就不是该整数的一部分。
* 该编码表示的整数值为 (0x8A & 0x7F) << 7 | 0x7B,
* 也就是这两个字节的后7位拼在一起构成的14-bit值
* 0x8A9B6C : 表示一个21-bit整数,因为第3个字节的首位为0,之后字节就不是该整数的一部分。
* 该编码表示的整数值为 ( (0x8A & 0x7F) << 7 | (0x9B & 0x7F) )<< 7 | 0x6C
* 也就是这三个字节的后7位拼在一起构成的21-bit值
* 0x8A9BAC6D : 表示一个29-bit整数,也就是前三个字节的后7位和最后一个字节拼在一起构成的29-bit值,
* 该表吗表示的整数值为 ( ( (0x8A & 0x7F) << 7 | (0x9B & 0x7F) )<< 7 | 0xAC ) << 8 | 0x6D
*
* @param data : [输入] 存有编码数据
* @param valp : [输出] 用于存放获取到的整数值
*
* @return 返回用于表示该整数的字节个数.
*/
int AMF3ReadInteger(const char *data, int32_t *valp);
/**
* @brief 从data中读取字符串,字符串格式(参考AMF3官方协议1.3.2节) :
*
* | 可变长度的无符号整数(1/2/3/4字节) | 字符串数据 (可选,依据前面整数最后一位确定) |
*
* 如果无符号整数值的最低位为0, 则该值表示字符串引用的索引指数。
* 如果无符号整数值的最低位为1, 则该值剩下的位数表示字符串的长度,其后跟字符串数据。
*
* @param data : [输入]原始数据区
* @param str : [输出] 结构体存放读取出来的字符串数据及长度
*
* @return 总共读取的字节个数.
*/
int AMF3ReadString(const char *data, AVal *str)
{
int32_t ref = 0;
int len;
assert(str != 0);
// 读取可变长度的无符号29位整数,该值保存在ref中,返回值len表示该整数值的字节个数
len = AMF3ReadInteger(data, &ref);
data += len;
// 如果无符号整数值的最低位为0,则表示字符串引用的编码,余下的位数用来编码字符串引用的索引指数
if ((ref & 0x1) == 0)
{
/* reference: 0xxx */
uint32_t refIndex = (ref >> 1);
return len;
}
else// 如果无符号整数值的最低位为1, 则字符串是字面上编码的,余下的位数用来表示UTF-8编码的字节的长度
{
uint32_t nSize = (ref >> 1);// 去掉最后一位,剩下的位数表示长度,所以左移一位
str->av_val = (char *)data;
str->av_len = nSize;
return len + nSize;
}
return len;
}
/**
* @brief 解码一个AMF3属性,包括属性的名字、类型及值。
* AMF3数据类型的定义见官方文档第3章。
*
* @param prop : [输出]用于保存解码出来的AMF3属性
* @param pBuffer : [输入]缓冲区存有已编码的AMF3属性
* @param nSize : [输入]pBuffer的大小
* @param bDecodeName : [输入]是否解码属性名字
*
* @return 解码过程中所读取的字节个数。
*/
int AMF3Prop_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize, int bDecodeName);
/**
* @brief 解码一个AMF属性,包括属性的名字、长度、类型和值
*
* @param prop : [输出]用于保存解码出来的AMF属性
* @param pBuffer : [输入]缓冲区存有已编码的AMF属性
* @param nSize : [输入]pBuffer的大小
* @param bDecodeName : [输入]是否解码属性名字
*
* @return 解码过程中所读取的字节个数。
*/
int AMFProp_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize, int bDecodeName)
{
... ... (省略部分代码)
if (bDecodeName)
{
// 获取属性名字字符串的长度,该长度值是一个short整型,占2个字节
unsigned short nNameSize = AMF_DecodeInt16(pBuffer);
// 属性名字的字符串长度比给定的数据长度还要长,表示名字长度超出范围
if (nNameSize > nSize - 2)
return -1;
// 解码字符串,前2个字节表示长度,其后是字符串
AMF_DecodeString(pBuffer, &prop->p_name);
nSize -= 2 + nNameSize;
pBuffer += 2 + nNameSize;
}
// 属性名字其后没有数据了,无法得到该名字对应的值,因此解码失败,返回-1。
if (nSize == 0)
return -1;
// 名字的值的类型占1个字节,所以此处nSize需要减1,*pBuffer就是类型
nSize--;
prop->p_type = *pBuffer++;
// AMF0数据类型介绍,参考AMF0官方文档第2章
switch (prop->p_type)
{
case AMF_NUMBER:// double值,8个字节
... ... (省略部分代码)
case AMF_BOOLEAN:// boolean值,1个字节
... ... (省略部分代码)
case AMF_STRING:// 字符串格式 : | 长度(2字节)| 真实字符串 |
... ... (省略部分代码)
case AMF_OBJECT:// AMF对象,里面存有该对象的各种属性
... ... (省略部分代码)
case AMF_MOVIECLIP:// 保留,但不支持
... ... (省略部分代码)
... ...(省略剩下的case)
}
return nOriginalSize - nSize;
}
/**
* @brief 输出显示AMF属性的名字和值
*/
void AMFProp_Dump(AMFObjectProperty *prop);
/**
* @brief 重置AMF属性
*/
void AMFProp_Reset(AMFObjectProperty *prop);
/**
* @brief 编码AMF对象,一个AMF对象包括多个AMF属性及其个数。
*/
char *AMF_Encode(AMFObject *obj, char *pBuffer, char *pBufEnd)
{
... ... (省略部分代码)
// 循环编码每一个AMF属性
for (i = 0; i < obj->o_num; i++)
{
char *res = AMFProp_Encode(&obj->o_props[i], pBuffer, pBufEnd);
... ... (省略部分代码)
}
if (pBuffer + 3 >= pBufEnd)
return NULL;/* no room for the end marker */
// 将AMF结束标志编码进去,占3个字节,即0x000009
pBuffer = AMF_EncodeInt24(pBuffer, pBufEnd, AMF_OBJECT_END);
return pBuffer;
}
/**
* @brief 解码一个属性数组,将解码出来的属性存放在AMF对象obj中
*
* @return 如果解码成功则返回读取的字节数,否则返回-1.
*/
int AMF_DecodeArray(AMFObject *obj, const char *pBuffer, int nSize, int nArrayLen, int bDecodeName);
/**
* @brief 解码一个AMF3数据
*
* @return 读取的字节数.
*/
int AMF3_Decode(AMFObject *obj, const char *pBuffer, int nSize, int bAMFData)
{
... ... (省略部分代码)
if ((ref & 1) == 0)// 不是实例,而是一个引用,参考AMF3协议3.12节关于U290-ref的解释
{
/* object reference, 0xxx */
uint32_t objectIndex = (ref >> 1);
RTMP_Log(RTMP_LOGDEBUG, "Object reference, index: %d", objectIndex);
}
else/* object instance,对象实例 */
{
int32_t classRef = (ref >> 1);
AMF3ClassDef cd = { {0, 0} };
AMFObjectProperty prop;
if ((classRef & 0x1) == 0)// 引用发送对象特性,参考AMF3协议3.12节关于U290-traits-ref的解释
{
/* class reference */
uint32_t classIndex = (classRef >> 1);
RTMP_Log(RTMP_LOGDEBUG, "Class reference: %d", classIndex);
}
else// 参考AMF3协议3.12节关于U290-traits-ext 和 U290-traits 的解释
{
int32_t classExtRef = (classRef >> 1);
int i;
cd.cd_externalizable = (classExtRef & 0x1) == 1; // 如果为1,则剩下的位数无意义(特性成员数量必须是0)
cd.cd_dynamic = ((classExtRef >> 1) & 0x1) == 1;// 是否动态,0表示不是动态的,1表示是动态的
cd.cd_num = classExtRef >> 2;// 编码封装好的特性成员的名称的数量,在它之后是类名
... ... (省略部分代码)
// 循环获取特性成员的名称,并将其添加到类cd中
for (i = 0; i < cd.cd_num; i++)
{
... ... (省略部分代码)
}
}
... ... (省略部分代码)
}
return nOriginalSize - nSize;
}
/**
* @brief 解码一个AMF对象
*
* @return 如果解码成功则返回读取的字节数,否则返回-1.
*/
int AMF_Decode(AMFObject *obj, const char *pBuffer, int nSize, int bDecodeName)
{
int nOriginalSize = nSize;
int bError = FALSE;/* if there is an error while decoding - try to at least find the end mark AMF_OBJECT_END */
obj->o_num = 0;
obj->o_props = NULL;
// 循环解码一系列AMF属性,将其添加到AMF对象obj中
while (nSize > 0)
{
AMFObjectProperty prop;
int nRes;
// 接下来的3个字节是AMF_OBJECT_END,表示AMF对象结束
if (nSize >=3 && AMF_DecodeInt24(pBuffer) == AMF_OBJECT_END)
{
nSize -= 3;
bError = FALSE;
break;
}
if (bError)
{
RTMP_Log(RTMP_LOGERROR, "DECODING ERROR, IGNORING BYTES UNTIL NEXT KNOWN PATTERN!");
nSize--;
pBuffer++;
continue;
}
// 解码一个AMF属性,属性相关信息存放在prop中,返回读取的字节个数
nRes = AMFProp_Decode(&prop, pBuffer, nSize, bDecodeName);
if (nRes == -1)
bError = TRUE;
else
{
nSize -= nRes;
pBuffer += nRes;
AMF_AddProp(obj, &prop);// 将解码出来的属性添加到AMF对象obj中
}
}
if (bError)
return -1;
return nOriginalSize - nSize;
}
/**
* @brief 将一个AMF属性添加到AMF对象的属性数组中。
*/
void AMF_AddProp(AMFObject *obj, const AMFObjectProperty *prop);
/**
* @brief 获取一个AMF对象的属性数目
*/
int AMF_CountProp(AMFObject *obj);
/**
* @brief 根据属性名字或者索引指数获取相应的AMF属性。优先考虑索引指数,
* 除非索引指数为负数,才考虑采用属性名字进行搜索。
*
* @return 搜索成功则返回相应的属性,否则返回无效的属性。
*/
AMFObjectProperty *AMF_GetProp(AMFObject *obj, const AVal *name, int nIndex);
/**
* @brief 输出显示一个AMF对象的所有属性的信息。
*/
void AMF_Dump(AMFObject *obj);
/**
* @brief 重置一个AMF对象,首先重置所有属性,释放属性空间。然后设置AMF属性为空,数目为0。
*/
void AMF_Reset(AMFObject *obj);
/**
* @brief 添加一个属性到类cd中.
*/
void AMF3CD_AddProp(AMF3ClassDef *cd, AVal *prop);
/**
* @brief 根据索引指数获取类cd的属性
*/
AVal *AMF3CD_GetProp(AMF3ClassDef *cd, int nIndex);