IM自定义协议解析说明
- IM的协议文档参见IM协议文档
一、IM协议简介
在IM的通信中,采用的是自定义的协议 + PB协议构成,所有的负载都是采用PB格式进行序列化和反序列化构成的
1.1协议的构成:
StartMagic + FixedHeader + AccountInfo + Payload
1.1.1、startMagic:魔数 0x22
魔数标记着协议的开始,或者说当有一条通信过来后,当检测到第一个字节是0x22后,我们认为这是我们需要的一条通信,魔数的大小占一个字节(1byte)
1.1.2、FixedHeader:固定头部
固定头部由一个命令字和可变的包体长度构成
- 命名字(cmd):请求的命令字,占一个字节(1byte),包含256个命令字
- 包体长度(remain_len) 包含AccountInfo 和 Payload的包体长度,占2-6个字节(2-6byte)
1.1.3、AccountInfo:账号信息
账号信息包含三个字段业务ID 、 终端的设备类型 和 账号名称
- 业务ID(AppID):用户接入的业务ID,用于区分是哪一个应用,占28位(28bit)
- 终端的设备类型(DeviceType):用于区分设备的终端类型,占4位(4bit)
- 账号名称(Username):用以唯一区分一个用户,统一使用UTF编码,传输的时候以0结尾
1.1.4、Payload:负载信息
协议通信的具体内容都是采用Protobuf来进行序列化和反序列化的
二、IM协议解析
ReadThread是一个一直在后台运行的线程,会不断的读取服务端下发下来的数据流,首次会默认读一个字节,就是魔数,如果魔数是我们定义的,就认为是我们自己的协议,在ReadController里面进行解析,ReadController这次解析完成后,会返回下次需要解析的字节大小给ReadThread,然后ReadThread在读相应的字节数组继续给ReadController进行解析,如此往复,直到整个数据流解析完成
2.1 解析魔数
魔数占一个字节,所以我们ReadThread在第一次收到数据流的时候,都是默认读一个字节,这里只需要将读到的魔数与我们规定的魔数(0x22)作比较,如果想等,代表是我们的自定义协议,下一步读取命令字,返回需要读取的一个字节给ReadThread
2.2 解析命令字
命令字(cmd)占一个字节,所以直接读取这一个字节的内容,下一步读取包体长度,返回需要读取的一个字节给ReadThread
2.3 解析包体长度
包体长度(payload_len)最长占4个字节,所以这里一个字节一个字节的读,如果这个字节的最高位为1,说明包体长度数据还没有读完,则继续读取下一个字节,如果最高位为0,说明包体长度的数据读完了。读完包体长度后读取APPID和DeviceType,返回需要读取的4个字节给ReadThread
示例代码:
//取一个字节中的低7位,并加上之前读取到的数
mPayLoadLen = (rec[0] & 0x7F) + (mPayLoadLen << 7);
//取最高位 最高位为0 代表payload的长度读完,最高位为1,说明后面还有数值,还需要继续读
if ((rec[0] & 0x80) == 0) {
if (mPayLoadLen > PAYLOAD_MAX_LENGTH) {
DebugLog.e(TAG, "recv:长度错误:mPayLoadLen: " + mPayLoadLen);
clear();
return 1;
}
mCurrent = ReadStep.READ_APPID;
DebugLog.d(TAG, "recv:mPayLoadLen: " + mPayLoadLen);
return APPID_LENGTH;
}
2.4 解析Appid和DeviceType
appid(28bit)和DeviceType(4bit)一共占四个字节
所以DeviceType是最好获取的,直接用第四个字节的的数据取最低四位就行了
示例代码:
//deviceType只占4位,所以只取最后一个字节的低四位
mDeviceType = rec[3] & 0xF;
读取Appid的时候,要注意一下,因为Appid占28位,所以在取第四个字节的时候,我们取的是高四位
代码示例:
/**
* 读取appid,Appid占28位,所以需要取第四个字节的最高四位
*/
mAppId = ((rec[0] << 24) + (rec[1] << 16) + (rec[2] << 8) + (rec[3] & 0xF0)) >> 4;
2.5读取Username
userName有一个结尾标志,所以每次读的时候,只能一个字节一个字节的读,当读到0的时候,就代表username读完了,没读完的话就继续读,并将这次读取的数据存在buf中数组中
另外需要注意的是,有些通信指令是没有payload信息的,比如登出,心跳等,所以如果在读完userName后,读取到的payload长度为0 了,就说明读完了,这时候就开始分发消息,如果payload长度不为0,就再次接着读
2.6读取paylaod信息
读取payload的时候,一次性最多读1024个字节,如果payload长度小于1024,就读取剩下的长度,把读取到的数据缓存在buf数组中
三、协议的封装
协议的封装主要就是按照上面的协议顺序,依次需要的数据添加到字节数组中
这一块就比较的简单,具体的代码参见ProtoedPayload
类