目标
根据前几章的学习,基本对MQTT协议的结构和解析过程有了一个了解. 本篇文章将通过编写解析控制头,加深对MQTT协议解析过程的理解
准备
- 参见 “MQTT 实践” ,“MQTT-Java 连接协议” 完成测试工程配置
数据包整体结构
固定头 + 可变头 + 剩余长度 + 数据负载体
备注:可参见 “上一篇” 理解
实验协议过程
连接 -> 发布主题 -> 休眠5秒 -> 订阅主题 -> 休眠5秒 -> 解除订阅 -> 休眠5秒 -> 订阅主题 -> 休眠5秒 -> 断开连接
日志效果
- 红色框为本次新增加的协议头解析效果
- 蓝色框作为对照,可以自行手动进行二次分析
2023-09-04 17:12:08:724 start connect remote server : 127.0.0.1:1883
2023-09-04 17:12:09:050 --------------------Header---------------------------
2023-09-04 17:12:09:050 类型 :CONNECT,QoS:0, DUP:0
2023-09-04 17:12:09:050 协议: MQTT V5
2023-09-04 17:12:09:050 ConnectFlags
UserNameFlag PasswordFlag WillRetain WillQoS WillFlag CleanStart Reserved
0 0 0 0 0 1 0
2023-09-04 17:12:09:053 心跳间隙: 30 秒
2023-09-04 17:12:09:053 session过期时间: 60秒
2023-09-04 17:12:09:053 主题别名最大值 : 30
2023-09-04 17:12:09:053 包最大尺寸: 1000
2023-09-04 17:12:09:053 Send 包类型:CONNECT
2023-09-04 17:12:09:054 Send Message Header(二进制内容) : 00010000 00101111 00000000 00000100 01001101 01010001 01010100 01010100 00000101 00000010 00000000 00011110 00001101 00010001 00000000 00000000 00000000 00111100 00100010 00000000 00011110 00100111 00000000 00000000 00000011 11101000
2023-09-04 17:12:09:054 Send Message payload(二进制内容) : 00000000 00010101 01001001 01101110 01110100 01100101 01001001 01001001 01101001 01001010 00100000 01001001 01000100 01000101 01000001 00100000 01000011 01101100 01101001 01100101 01101110 01110100 00100000
2023-09-04 17:12:09:055 --------------------Header---------------------------
2023-09-04 17:12:09:055 类型 :CONNACK,QoS:0, DUP:0
2023-09-04 17:12:09:056 sessionPresent: 0
2023-09-04 17:12:09:056 连接结果 : Success
2023-09-04 17:12:09:056 最大接收 : 20
2023-09-04 17:12:09:056 主题别名最大值 : 10
2023-09-04 17:12:09:056 Receive Message Header(二进制内容) : 00100000 00001001 00000000 00000000 00000110 00100001 00000000 00010100 00100010 00000000 00001010
2023-09-04 17:12:09:060 --------------------Header---------------------------
2023-09-04 17:12:09:060 类型 :PUBLISH,QoS:4, DUP:0
2023-09-04 17:12:09:060 主题: harvey
2023-09-04 17:12:09:060 包标识: 1
2023-09-04 17:12:09:060 负荷标识: 1
2023-09-04 17:12:09:060 消息过期时间: 3600
2023-09-04 17:12:09:060 内容类型: TEST_PUBLISH
2023-09-04 17:12:09:060 回应主题: quick
2023-09-04 17:12:09:061 对比数据: HW
2023-09-04 17:12:09:061 主题别名: 1
2023-09-04 17:12:09:061 属性: first=firstValue
2023-09-04 17:12:09:061 属性: second=secondValue
2023-09-04 17:12:09:061 Send 包类型:PUBLISH
2023-09-04 17:12:09:061 Send Message Header(二进制内容) : 00110100 01100011 00000000 00000110 01101000 01100001 01110010 01110110 01100101 01111001 00000000 00000001 01010000 00000001 00000001 00000010 00000000 00000000 00001110 00010000 00000011 00000000 00001100 01010100 01000101 01010011 01010100 01011111 01010000 01010101 01000010 01001100 01001001 01010011 01001000 00001000 00000000 00000101 01110001 01110101 01101001 01100011 01101011 00001001 00000000 00000010 01001000 01010111 00100011 00000000 00000001 00100110 00000000 00000101 01100110 01101001 01110010 01110011 01110100 00000000 00001010 01100110 01101001 01110010 01110011 01110100 01010110 01100001 01101100 01110101 01100101 00100110 00000000 00000110 01110011 01100101 01100011 01101111 01101110 01100100 00000000 00001011 01110011 01100101 01100011 01101111 01101110 01100100 01010110 01100001 01101100 01110101 01100101
2023-09-04 17:12:09:061 Send Message payload(二进制内容) : 01010011 01101000 01100001 01101110 01100111 01001000 01100001 01101001
2023-09-04 17:12:09:062 --------------------Header---------------------------
2023-09-04 17:12:09:062 类型 :PUBREC,QoS:0, DUP:0
2023-09-04 17:12:09:062 Packet Identifier: 1
2023-09-04 17:12:09:062 Receive Message Header(二进制内容) : 01010000 00000010 00000000 00000001
2023-09-04 17:12:09:063 --------------------Header---------------------------
2023-09-04 17:12:09:063 类型 :PUBREL,QoS:2, DUP:0
2023-09-04 17:12:09:063 Packet Identifier: 1
2023-09-04 17:12:09:063 Send 包类型:PUBREL
2023-09-04 17:12:09:063 Send Message Header(二进制内容) : 01100010 00000010 00000000 00000001
2023-09-04 17:12:09:063 Send Message payload(二进制内容) :
2023-09-04 17:12:09:064 --------------------Header---------------------------
2023-09-04 17:12:09:064 类型 :PUBCOMP,QoS:0, DUP:0
2023-09-04 17:12:09:064 Packet Identifier: 1
2023-09-04 17:12:09:064 Receive Message Header(二进制内容) : 01110000 00000010 00000000 00000001
2023-09-04 17:12:14:069 --------------------Header---------------------------
2023-09-04 17:12:14:069 类型 :SUBSCRIBE,QoS:2, DUP:0
2023-09-04 17:12:14:069 Packet Identifier: 2
2023-09-04 17:12:14:069 Send 包类型:SUBSCRIBE
2023-09-04 17:12:14:069 Send Message Header(二进制内容) : 10000010 00001100 00000000 00000010 00000000
2023-09-04 17:12:14:070 Send Message payload(二进制内容) : 00000000 00000110 01101000 01100001 01110010 01110110 01100101 01111001 00000001
2023-09-04 17:12:14:071 --------------------Header---------------------------
2023-09-04 17:12:14:071 类型 :SUBACK,QoS:0, DUP:0
2023-09-04 17:12:14:071 Packet Identifier: 2
2023-09-04 17:12:14:071 Receive Message Header(二进制内容) : 10010000 00000100 00000000 00000010 00000000
2023-09-04 17:12:14:071 Receive Message payload(二进制内容) : 00000001
2023-09-04 17:12:19:075 --------------------Header---------------------------
2023-09-04 17:12:19:075 类型 :UNSUBSCRIBE,QoS:2, DUP:0
2023-09-04 17:12:19:076 Packet Identifier: 3
2023-09-04 17:12:19:076 Send 包类型:UNSUBSCRIBE
2023-09-04 17:12:19:076 Send Message Header(二进制内容) : 10100010 00001011 00000000 00000011 00000000
2023-09-04 17:12:19:077 Send Message payload(二进制内容) : 00000000 00000110 01101000 01100001 01110010 01110110 01100101 01111001
2023-09-04 17:12:19:078 --------------------Header---------------------------
2023-09-04 17:12:19:078 类型 :UNSUBACK,QoS:0, DUP:0
2023-09-04 17:12:19:080 Packet Identifier: 3
2023-09-04 17:12:19:080 Receive Message Header(二进制内容) : 10110000 00000100 00000000 00000011 00000000
2023-09-04 17:12:19:080 Receive Message payload(二进制内容) : 00000000
2023-09-04 17:12:24:083 --------------------Header---------------------------
2023-09-04 17:12:24:084 类型 :SUBSCRIBE,QoS:2, DUP:0
2023-09-04 17:12:24:084 Packet Identifier: 4
2023-09-04 17:12:24:084 Send 包类型:SUBSCRIBE
2023-09-04 17:12:24:084 Send Message Header(二进制内容) : 10000010 00001100 00000000 00000100 00000000
2023-09-04 17:12:24:085 Send Message payload(二进制内容) : 00000000 00000110 01101000 01100001 01110010 01110110 01100101 01111001 00000001
2023-09-04 17:12:24:087 --------------------Header---------------------------
2023-09-04 17:12:24:087 类型 :SUBACK,QoS:0, DUP:0
2023-09-04 17:12:24:088 Packet Identifier: 4
2023-09-04 17:12:24:088 Receive Message Header(二进制内容) : 10010000 00000100 00000000 00000100 00000000
2023-09-04 17:12:24:088 Receive Message payload(二进制内容) : 00000001
2023-09-04 17:12:29:097 --------------------Header---------------------------
2023-09-04 17:12:29:098 类型 :DISCONNECT,QoS:0, DUP:0
2023-09-04 17:12:29:098 断开结果 : Normal disconnection
2023-09-04 17:12:29:099 Send 包类型:DISCONNECT
2023-09-04 17:12:29:099 Send Message Header(二进制内容) : 11100000 00000010 00000000 00000000
2023-09-04 17:12:29:100 Send Message payload(二进制内容) :
实验代码
package org.eclipse.paho.mqttv5.client.logging;
import org.eclipse.paho.mqttv5.common.packet.util.VariableByteInteger;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
public class HarveyDebug {
public final static int DEBUG_MODE = 1; //0: 默认模式 1: 解析模式
private static String date(){
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");//设置日期格式
return df.format(new Date());// new Date()为获取当前系统时间,也可使用当前时间戳
}
public static void d(String info){
System.out.println(date() + " " + info);
}
public static void d(){
System.out.println("");
}
public static void e(String info){
System.err.println(date() + " " + info);
}
public static void parseHeader(byte [] datas){
if(datas == null || datas.length == 0){
return;
}
d("--------------------Header---------------------------");
int currentByteIndex = 0;
int packetType = (datas[currentByteIndex] >> 4) & 0xf;
int QoS = (datas[currentByteIndex]&0x6) >> 1;
int dup = (datas[currentByteIndex]&0x8) >> 3;
currentByteIndex++;
// int remainingLength = datas[currentByteIndex];
VariableByteInteger remainVariableByte = variableByte(datas,currentByteIndex);
int remainingLength = remainVariableByte.getValue();
currentByteIndex = currentByteIndex + remainVariableByte.getEncodedLength() - 1;
if(packetType == 3){ //PUBLISH
d("类型 :PUBLISH," + "QoS:" + QoS + ", DUP:"+dup);
currentByteIndex++;
int topicNameLength = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++];
byte [] topicNameB = new byte[topicNameLength];
for(int k = 0; k < topicNameLength; k++){
topicNameB[k] = datas[currentByteIndex+k];
}
currentByteIndex = currentByteIndex + topicNameLength;
String topicName = new String(topicNameB, StandardCharsets.UTF_8);
d("主题: " + topicName);
if(QoS > 0){
int packetIdentifier = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex];
currentByteIndex++;
d("包标识: " + packetIdentifier);
}
//属性长度
VariableByteInteger propertyVariableByte = variableByte(datas,currentByteIndex);
int propertyLength = propertyVariableByte.getValue();
currentByteIndex = currentByteIndex + remainVariableByte.getEncodedLength();
int propertyType = datas[currentByteIndex];
while (currentByteIndex < datas.length){
if(propertyType == 1){ // Payload Format Indicator
currentByteIndex++;
int payloadIdentifier = datas[currentByteIndex];
currentByteIndex++;
d("负荷标识: " + payloadIdentifier);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 2) { //Message Expiry Interval`
currentByteIndex++;
int messageExpiryInterval = (datas[currentByteIndex++] << 24)&0xff000000 | (datas[currentByteIndex++] << 16)&0xff0000 | (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
d("消息过期时间: " +messageExpiryInterval);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 35) { //Topic Alias
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
d("主题别名: " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 8) { //Response Topic
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] responseTopicB = new byte[value];
for (int k = 0; k < value; k++){
responseTopicB[k] = datas[currentByteIndex++];
}
d("回应主题: " + new String(responseTopicB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 9) { //Correlation Data
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] correlationDataB = new byte[value];
for (int k = 0; k < value; k++){
correlationDataB[k] = datas[currentByteIndex++];
}
d("对比数据: " + new String(correlationDataB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 38) { //User Property
currentByteIndex++;
int key = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] keyB = new byte[key];
for (int k = 0; k < key; k++){
keyB[k] = datas[currentByteIndex++];
}
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] valueB = new byte[value];
for (int k = 0; k < value; k++){
valueB[k] = datas[currentByteIndex++];
}
d("属性: " + new String(keyB, StandardCharsets.UTF_8) + "=" + new String(valueB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 11) { //Subscription Identifier
currentByteIndex++;
VariableByteInteger payloadVariableByte = variableByte(datas,currentByteIndex);
int value = payloadVariableByte.getValue();//datas[currentByteIndex];
currentByteIndex = currentByteIndex + remainVariableByte.getEncodedLength() - 1;
d("订阅标识: " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 3) { //Content Type
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] contentTypeB = new byte[value];
for (int k = 0; k < value; k++){
contentTypeB[k] = datas[currentByteIndex++];
}
d("内容类型: " + new String(contentTypeB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
}
} else if(packetType == 1){//CONNECT
d("类型 :CONNECT," + "QoS:" + QoS + ", DUP:"+dup);
currentByteIndex++;
int protocalNameLength = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++];
byte [] protocalNameB = new byte[protocalNameLength];
for(int k = 0; k < protocalNameLength; k++){
protocalNameB[k] = datas[4+k];
currentByteIndex = 4+k;
}
currentByteIndex++;
int protocalVersion = datas[currentByteIndex];
String topicName = new String(protocalNameB, StandardCharsets.UTF_8);
d("协议: " + topicName + " V" +protocalVersion);
currentByteIndex++;
d("ConnectFlags");
//Connect Flags
System.out.printf("%12s %12s %11s %8s %9s %10s %8s",
"UserNameFlag", "PasswordFlag", "WillRetain",
"WillQoS",
"WillFlag","CleanStart","Reserved");
System.out.println();
System.out.format("%12d %12d %11d %8s %9d %10d %8d",
(datas[currentByteIndex]&0x80)>>7, (datas[currentByteIndex]&0x40)>>6, (datas[currentByteIndex]&0x20)>>5,
((datas[currentByteIndex]&0x10)>>4) + "" + ((datas[currentByteIndex]&0x8)>>3),
(datas[currentByteIndex]&0x4)>>2, (datas[currentByteIndex]&0x02)>>1, datas[currentByteIndex]&0x1);
System.out.println();
currentByteIndex++;
int keepLiveLength = (datas[currentByteIndex++]<<8)&0xff00 | datas[currentByteIndex++];
d("心跳间隙: " + keepLiveLength + " 秒");
//属性长度
VariableByteInteger propertyVariableByte = variableByte(datas,currentByteIndex);
int propertyLength = propertyVariableByte.getValue();
currentByteIndex = currentByteIndex + remainVariableByte.getEncodedLength();
if(currentByteIndex >= datas.length){
return;
}
int propertyType = datas[currentByteIndex];
while (currentByteIndex < datas.length){
if(propertyType == 17){ //Session Expiry Interval
currentByteIndex++;
int sessionExpiry = (datas[currentByteIndex++] << 24)&0xff000000 | (datas[currentByteIndex++] << 16)&0xff0000 | (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
d("session过期时间: " + sessionExpiry + "秒");
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 33){ //Receive Maximum
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
d("最大接收 : " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 39){ //Maximum Packet Size
currentByteIndex++;
int value = (datas[currentByteIndex++] << 24)&0xff000000 | (datas[currentByteIndex++] << 16)&0xff0000 | (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
d("包最大尺寸: " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 34){ //Topic Alias Maximum
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
d("主题别名最大值 : " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 25){ //Request Response Information
currentByteIndex++;
int value = datas[currentByteIndex++]&0xff;
d("Identifier of the Request Response Information : " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 23){ //Request Problem Information
currentByteIndex++;
int value = datas[currentByteIndex++]&0xff;
d("Identifier of the Request Problem Information : " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 38) { //User Property
currentByteIndex++;
int key = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] keyB = new byte[key];
for (int k = 0; k < key; k++){
keyB[k] = datas[currentByteIndex++];
}
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] valueB = new byte[value];
for (int k = 0; k < value; k++){
valueB[k] = datas[currentByteIndex++];
}
d("属性: " + new String(keyB, StandardCharsets.UTF_8) + "=" + new String(valueB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 21){ //Authentication Method
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] contentTypeB = new byte[value];
for (int k = 0; k < value; k++){
contentTypeB[k] = datas[currentByteIndex++];
}
d("Authentication Method: " + new String(contentTypeB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 22){ //Authentication Data
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] contentTypeB = new byte[value];
for (int k = 0; k < value; k++){
contentTypeB[k] = datas[currentByteIndex++];
}
d("Authentication Data: " + new String(contentTypeB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
}
} else if(packetType == 2) {//CONNACK
d("类型 :CONNACK," + "QoS:" + QoS + ", DUP:" + dup);
//Connect Acknowledge Flags, Connect Reason Code, and Properties
currentByteIndex++;
int sessionPresent = datas[currentByteIndex++]&0x1;
d("sessionPresent: " + sessionPresent);
int connectReasonCode = datas[currentByteIndex++]&0xff;
String reason = "unknow";
if(connectReasonCode == 0){
reason = "Success";
} else if(connectReasonCode == 0x80){
reason = "Unspecified error";
} else if(connectReasonCode == 0x81){
reason = "UMalformed Packet";
} else if(connectReasonCode == 0x82){
reason = "Protocol Error";
} else if(connectReasonCode == 0x83){
reason = "Implementation specific error";
} else if(connectReasonCode == 0x84){
reason = "Unsupported Protocol Version";
} else if(connectReasonCode == 0x85){
reason = "UClient Identifier not valid";
} else if(connectReasonCode == 0x86){
reason = "Bad User Name or Password";
} else if(connectReasonCode == 0x87){
reason = "Not authorized";
} else if(connectReasonCode == 0x88){
reason = "Server unavailable";
} else if(connectReasonCode == 0x89){
reason = "Server busy";
} else if(connectReasonCode == 0x8a){
reason = "Banned";
} else if(connectReasonCode == 0x8c){
reason = "Bad authentication method";
} else if(connectReasonCode == 0x90){
reason = "Topic Name invalid";
} else if(connectReasonCode == 0x95){
reason = "Packet too large";
} else if(connectReasonCode == 0x97){
reason = "Quota exceeded";
} else if(connectReasonCode == 0x99){
reason = "Payload format invalid";
} else if(connectReasonCode == 0x9a){
reason = "Retain not supported ";
} else if(connectReasonCode == 0x9b){
reason = "QoS not supported";
} else if(connectReasonCode == 0x9c){
reason = "Use another server";
} else if(connectReasonCode == 0x9d){
reason = "Server moved";
} else if(connectReasonCode == 0x9f){
reason = "Connection rate exceeded";
}
d("连接结果 : " + reason);
//属性长度
VariableByteInteger propertyVariableByte = variableByte(datas,currentByteIndex);
int propertyLength = propertyVariableByte.getValue();
currentByteIndex = currentByteIndex + remainVariableByte.getEncodedLength();
if(currentByteIndex >= datas.length){
return;
}
int propertyType = datas[currentByteIndex];
while (currentByteIndex < datas.length) {
if (propertyType == 17) { //Session Expiry Interval
currentByteIndex++;
int sessionExpiry = (datas[currentByteIndex++] << 24) & 0xff000000 | (datas[currentByteIndex++] << 16) & 0xff0000 | (datas[currentByteIndex++] << 8) & 0xff00 | datas[currentByteIndex++] & 0xff;
d("session过期时间: " + sessionExpiry + "秒");
if (currentByteIndex >= datas.length) {
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 33){ //Receive Maximum
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
d("最大接收 : " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 36){ // Maximum QoS
currentByteIndex++;
int value = datas[currentByteIndex++]&0xff;
d("Maximum QoS : " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 37){ // Retain Available
currentByteIndex++;
int value = datas[currentByteIndex++]&0xff;
d("Retain Available : " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 39){ //Maximum Packet Size
currentByteIndex++;
int value = (datas[currentByteIndex++] << 24)&0xff000000 | (datas[currentByteIndex++] << 16)&0xff0000 | (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
d("包最大尺寸: " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 18){ //Assigned Client Identifier
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] contentTypeB = new byte[value];
for (int k = 0; k < value; k++){
contentTypeB[k] = datas[currentByteIndex++];
}
d("Assigned Client Identifier: " + new String(contentTypeB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 34){ //Topic Alias Maximum
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
d("主题别名最大值 : " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 31){ //Reason String
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
d("Reason String : " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 38) { //User Property
currentByteIndex++;
int key = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] keyB = new byte[key];
for (int k = 0; k < key; k++){
keyB[k] = datas[currentByteIndex++];
}
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] valueB = new byte[value];
for (int k = 0; k < value; k++){
valueB[k] = datas[currentByteIndex++];
}
d("属性: " + new String(keyB, StandardCharsets.UTF_8) + "=" + new String(valueB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 40){ // Wildcard Subscription Available
currentByteIndex++;
int value = datas[currentByteIndex++]&0xff;
d("Wildcard Subscription Available : " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 41){ // Subscription Identifiers Available
currentByteIndex++;
int value = datas[currentByteIndex++]&0xff;
d("Subscription Identifiers Available : " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 42){ // Shared Subscription Available
currentByteIndex++;
int value = datas[currentByteIndex++]&0xff;
d("Shared Subscription Available : " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 19){ //Server Keep Alive
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
d("Server Keep Alive : " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 26) { //Response Information
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] responseTopicB = new byte[value];
for (int k = 0; k < value; k++){
responseTopicB[k] = datas[currentByteIndex++];
}
d("Response Information: " + new String(responseTopicB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 28) { //Server Reference
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] responseTopicB = new byte[value];
for (int k = 0; k < value; k++){
responseTopicB[k] = datas[currentByteIndex++];
}
d("Server Reference: " + new String(responseTopicB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 21){ //Authentication Method
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] contentTypeB = new byte[value];
for (int k = 0; k < value; k++){
contentTypeB[k] = datas[currentByteIndex++];
}
d("Authentication Method: " + new String(contentTypeB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 22){ //Authentication Data
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] contentTypeB = new byte[value];
for (int k = 0; k < value; k++){
contentTypeB[k] = datas[currentByteIndex++];
}
d("Authentication Data: " + new String(contentTypeB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
}
} else if(packetType == 4 || packetType == 5 || packetType == 6 || packetType == 7) {//PUBACK / PUBREC / PUBREL / PUBCOMP
if(packetType == 4){
d("类型 :PUBACK," + "QoS:" + QoS + ", DUP:" + dup);
} else if(packetType == 5){
d("类型 :PUBREC," + "QoS:" + QoS + ", DUP:" + dup);
} else if(packetType == 6){
d("类型 :PUBREL," + "QoS:" + QoS + ", DUP:" + dup);
} else {
d("类型 :PUBCOMP," + "QoS:" + QoS + ", DUP:" + dup);
}
currentByteIndex++;
int identifier = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
d("Packet Identifier: " + identifier);
if(currentByteIndex >= datas.length){
return;
}
int code = datas[currentByteIndex++]&0xff;
String reason = "";
if(code == 0x00){
reason = "Success";
} else if(code == 0x10){
reason = "No matching subscribers";
} else if(code == 0x80){
reason = "Unspecified error";
} else if(code == 0x83){
reason = "Implementation specific error";
} else if(code == 0x87){
reason = "Not authorized";
} else if(code == 0x90){
reason = "Topic Name invalid";
} else if(code == 0x91){
reason = "Packet identifier in use";
} else if(code == 0x92){
reason = "Packet Identifier not found";
} else if(code == 0x97){
reason = "Quota exceeded";
} else if(code == 0x99){
reason = "Payload format invalid";
}
d("发布主题结果 : " + reason);
//属性长度
VariableByteInteger propertyVariableByte = variableByte(datas,currentByteIndex);
int propertyLength = propertyVariableByte.getValue();
currentByteIndex = currentByteIndex + remainVariableByte.getEncodedLength();
if(currentByteIndex >= datas.length){
return;
}
int propertyType = datas[currentByteIndex];
while (currentByteIndex < datas.length) {
if(propertyType == 31){ //Reason String
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
d("Reason String : " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 38) { //User Property
currentByteIndex++;
int key = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] keyB = new byte[key];
for (int k = 0; k < key; k++){
keyB[k] = datas[currentByteIndex++];
}
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] valueB = new byte[value];
for (int k = 0; k < value; k++){
valueB[k] = datas[currentByteIndex++];
}
d("属性: " + new String(keyB, StandardCharsets.UTF_8) + "=" + new String(valueB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
}
} else if(packetType == 8){//SUBSCRIBE
d("类型 :SUBSCRIBE," + "QoS:" + QoS + ", DUP:" + dup);
currentByteIndex++;
int identifier = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
d("Packet Identifier: " + identifier);
//属性长度
VariableByteInteger propertyVariableByte = variableByte(datas,currentByteIndex);
int propertyLength = propertyVariableByte.getValue();
currentByteIndex = currentByteIndex + remainVariableByte.getEncodedLength();
if(currentByteIndex >= datas.length){
return;
}
int propertyType = datas[currentByteIndex];
while (currentByteIndex < datas.length){
if(propertyType == 11) { //Subscription Identifier
currentByteIndex++;
VariableByteInteger payloadVariableByte = variableByte(datas,currentByteIndex);
int value = payloadVariableByte.getValue();
currentByteIndex = currentByteIndex + remainVariableByte.getEncodedLength() - 1;
d("订阅标识: " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 38) { //User Property
currentByteIndex++;
int key = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] keyB = new byte[key];
for (int k = 0; k < key; k++){
keyB[k] = datas[currentByteIndex++];
}
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] valueB = new byte[value];
for (int k = 0; k < value; k++){
valueB[k] = datas[currentByteIndex++];
}
d("属性: " + new String(keyB, StandardCharsets.UTF_8) + "=" + new String(valueB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
}
} else if(packetType == 9){//SUBACK
d("类型 :SUBACK," + "QoS:" + QoS + ", DUP:" + dup);
currentByteIndex++;
int identifier = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
d("Packet Identifier: " + identifier);
//属性长度
VariableByteInteger propertyVariableByte = variableByte(datas,currentByteIndex);
int propertyLength = propertyVariableByte.getValue();
currentByteIndex = currentByteIndex + remainVariableByte.getEncodedLength();
if(currentByteIndex >= datas.length){
return;
}
int propertyType = datas[currentByteIndex];
while (currentByteIndex < datas.length){
if(propertyType == 31){ //Reason String
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
d("Reason String : " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 38) { //User Property
currentByteIndex++;
int key = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] keyB = new byte[key];
for (int k = 0; k < key; k++){
keyB[k] = datas[currentByteIndex++];
}
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] valueB = new byte[value];
for (int k = 0; k < value; k++){
valueB[k] = datas[currentByteIndex++];
}
d("属性: " + new String(keyB, StandardCharsets.UTF_8) + "=" + new String(valueB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
}
} else if(packetType == 10 || packetType == 11) {//UNSUBSCRIBE / UNSUBACK
if(packetType == 10){
d("类型 :UNSUBSCRIBE," + "QoS:" + QoS + ", DUP:" + dup);
} else {
d("类型 :UNSUBACK," + "QoS:" + QoS + ", DUP:" + dup);
}
currentByteIndex++;
int identifier = (datas[currentByteIndex++] << 8) & 0xff00 | datas[currentByteIndex++] & 0xff;
d("Packet Identifier: " + identifier);
//属性长度
VariableByteInteger propertyVariableByte = variableByte(datas,currentByteIndex);
int propertyLength = propertyVariableByte.getValue();
currentByteIndex = currentByteIndex + remainVariableByte.getEncodedLength();
if (currentByteIndex >= datas.length) {
return;
}
int propertyType = datas[currentByteIndex];
while (currentByteIndex < datas.length) {
if(propertyType == 38) { //User Property
currentByteIndex++;
int key = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] keyB = new byte[key];
for (int k = 0; k < key; k++){
keyB[k] = datas[currentByteIndex++];
}
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] valueB = new byte[value];
for (int k = 0; k < value; k++){
valueB[k] = datas[currentByteIndex++];
}
d("属性: " + new String(keyB, StandardCharsets.UTF_8) + "=" + new String(valueB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
}
} else if(packetType == 12 || packetType == 13) {//PINGREQ / PINGRESP
if(packetType == 12){
d("类型 :PINGREQ," + "QoS:" + QoS + ", DUP:" + dup);
} else {
d("类型 :PINGRESP," + "QoS:" + QoS + ", DUP:" + dup);
}
} else if(packetType == 14) {//DISCONNECT
d("类型 :DISCONNECT," + "QoS:" + QoS + ", DUP:" + dup);
currentByteIndex++;
int code = datas[currentByteIndex++]&0xff;
String reason = "";
if(code == 0x00){
reason = "Normal disconnection";
} else if(code == 0x04){
reason = "Disconnect with Will Message";
} else if(code == 0x80){
reason = "Unspecified error";
} else if(code == 0x81){
reason = "Malformed Packet";
} else if(code == 0x82){
reason = "Protocol Error";
} else if(code == 0x83){
reason = "Implementation specific error";
} else if(code == 0x87){
reason = "Not authorized";
} else if(code == 0x89){
reason = "Server busy";
} else if(code == 0x8B){
reason = "Server shutting down";
} else if(code == 0x8D){
reason = "Keep Alive timeout";
} else if(code == 0x8E){
reason = "Session taken over";
} else if(code == 0x8F){
reason = "Topic Filter invalid";
} else if(code == 0x90){
reason = "Topic Name invalid";
} else if(code == 0x93){
reason = "Receive Maximum exceeded";
} else if(code == 0x94){
reason = "Topic Alias invalid";
} else if(code == 0x95){
reason = "Packet too large";
} else if(code == 0x96){
reason = "Message rate too high";
} else if(code == 0x97){
reason = "Quota exceeded";
} else if(code == 0x98){
reason = "Administrative action";
} else if(code == 0x99){
reason = "Payload format invalid";
} else if(code == 0x9A){
reason = "Retain not supported";
} else if(code == 0x9B){
reason = "QoS not supported";
} else if(code == 0x9C){
reason = "Use another server";
} else if(code == 0x9D){
reason = "Server moved";
} else if(code == 0x9E){
reason = "Shared Subscriptions not supported";
} else if(code == 0x9F){
reason = "Connection rate exceeded";
} else if(code == 0xA0){
reason = "Maximum connect time";
} else if(code == 0xA1){
reason = "Subscription Identifiers not supported";
} else if(code == 0xA2){
reason = "Wildcard Subscriptions not supported";
}
d("断开结果 : " + reason);
//属性长度
VariableByteInteger propertyVariableByte = variableByte(datas,currentByteIndex);
int propertyLength = propertyVariableByte.getValue();
currentByteIndex = currentByteIndex + remainVariableByte.getEncodedLength();
if(currentByteIndex >= datas.length){
return;
}
int propertyType = datas[currentByteIndex];
while (currentByteIndex < datas.length){
if(propertyType == 17){ //Session Expiry Interval
currentByteIndex++;
int sessionExpiry = (datas[currentByteIndex++] << 24)&0xff000000 | (datas[currentByteIndex++] << 16)&0xff0000 | (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
d("session过期时间: " + sessionExpiry + "秒");
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 31){ //Reason String
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
d("Reason String : " + value);
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 38) { //User Property
currentByteIndex++;
int key = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] keyB = new byte[key];
for (int k = 0; k < key; k++){
keyB[k] = datas[currentByteIndex++];
}
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] valueB = new byte[value];
for (int k = 0; k < value; k++){
valueB[k] = datas[currentByteIndex++];
}
d("属性: " + new String(keyB, StandardCharsets.UTF_8) + "=" + new String(valueB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
if(propertyType == 28) { //Server Reference
currentByteIndex++;
int value = (datas[currentByteIndex++] << 8)&0xff00 | datas[currentByteIndex++]&0xff;
byte [] responseTopicB = new byte[value];
for (int k = 0; k < value; k++){
responseTopicB[k] = datas[currentByteIndex++];
}
d("Server Reference: " + new String(responseTopicB, StandardCharsets.UTF_8));
if(currentByteIndex >= datas.length){
break;
}
propertyType = datas[currentByteIndex];
}
}
}
}
public static VariableByteInteger variableByte(byte [] datas, int currentByteIndex){
byte digit;
int value = 0;
int multiplier = 1;
int count = 0;
do {
digit = datas[currentByteIndex++];
count++;
value += ((digit & 0x7F) * multiplier);
multiplier *= 128;
} while ((digit & 0x80) != 0);
return new VariableByteInteger(value, count);
}
}
package com.harvey.mqtt;
import org.eclipse.paho.mqttv5.client.*;
import org.eclipse.paho.mqttv5.client.logging.HarveyDebug;
import org.eclipse.paho.mqttv5.client.persist.MemoryPersistence;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.MqttMessage;
import org.eclipse.paho.mqttv5.common.MqttSubscription;
import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
import org.eclipse.paho.mqttv5.common.packet.UserProperty;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* 关于 PUBLISH 协议的详细研究
*
*/
public class ExamplePublishDetail {
public static void main(String[] args) {
publish();
}
/**
*
* 发布主题内容
*
*/
private static void publish(){
MemoryPersistence persistence = new MemoryPersistence();
try {
MqttConnectionOptions connOpts = new MqttConnectionOptions();
connOpts.setCleanStart(true);
connOpts.setKeepAliveInterval(30);
connOpts.setSessionExpiryInterval(60L);
connOpts.setMaximumPacketSize(1000L);
connOpts.setTopicAliasMaximum(30);
MqttAsyncClient sampleClient = new MqttAsyncClient(MQTTConfigue.broker, MQTTConfigue.clientId, persistence);
IMqttToken token = sampleClient.connect(connOpts);
token.waitForCompletion();
MqttMessage message = new MqttMessage();
message.setQos(2);
MqttProperties mqttProperties = new MqttProperties();
mqttProperties.setCorrelationData("HW".getBytes(StandardCharsets.UTF_8));
mqttProperties.setTopicAlias(9);
mqttProperties.setSessionExpiryInterval(50L);
mqttProperties.setContentType("TEST_PUBLISH");
mqttProperties.setPayloadFormat(true);
mqttProperties.setResponseTopic("quick");
mqttProperties.setReasonString("helloword");
mqttProperties.setMessageExpiryInterval(60*60L);
mqttProperties.setTopicAliasMaximum(6000);
List<UserProperty> userPropertyList = new ArrayList<>();
userPropertyList.add(new UserProperty("first", "firstValue"));
userPropertyList.add(new UserProperty("second", "secondValue"));
mqttProperties.setUserProperties(userPropertyList);
mqttProperties.setMaximumQoS(10);
message.setProperties(mqttProperties);
byte [] content = "ShangHai".getBytes(StandardCharsets.UTF_8);
message.setPayload(content);
sampleClient.publish(MQTTConfigue.topic, message);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
subscription(sampleClient);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sampleClient.unsubscribe(MQTTConfigue.topic);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
subscription(sampleClient);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sampleClient.disconnect();
} catch(MqttException me) {
System.out.println("reason "+me.getReasonCode());
System.out.println("msg "+me.getMessage());
System.out.println("loc "+me.getLocalizedMessage());
System.out.println("cause "+me.getCause());
System.out.println("excep "+me);
me.printStackTrace();
}
}
private static void subscription(MqttAsyncClient sampleClient) throws MqttException {
MqttSubscription subscription = new MqttSubscription(MQTTConfigue.topic);
MqttProperties mqttProperties = new MqttProperties();
mqttProperties.setPayloadFormat(true);
sampleClient.subscribe(new MqttSubscription[]{subscription}, null, new MqttActionListener() {
@Override
public void onSuccess(IMqttToken asyncActionToken) {
}
@Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
}
}, new IMqttMessageListener() {
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
}
}, mqttProperties);
}
/**
*
* 订阅主题,验证接收QoS0,QoS1,QoS2级别的消息
*
*/
private static void subscribe(){
MemoryPersistence persistence = new MemoryPersistence();
try {
MqttConnectionOptions connOpts = new MqttConnectionOptions();
connOpts.setCleanStart(false);
connOpts.setKeepAliveInterval(0);
connOpts.setSessionExpiryInterval(60L);
MqttAsyncClient sampleClient = new MqttAsyncClient(MQTTConfigue.broker, MQTTConfigue.clientId, persistence);
IMqttToken token = sampleClient.connect(connOpts);
token.waitForCompletion();
subscription(sampleClient);
} catch(MqttException me) {
System.out.println("reason "+me.getReasonCode());
System.out.println("msg "+me.getMessage());
System.out.println("loc "+me.getLocalizedMessage());
System.out.println("cause "+me.getCause());
System.out.println("excep "+me);
me.printStackTrace();
}
}
}
协议解析理解总结
- 牢记数据包整体结构
- 牢记规范中的数据类型及占用内容和含义
- 每个协议中的属性,类型标识在规范中是完全一致的。比如:用户属性,其标识为10进制38,在PUBLISH协议,CONNACK协议中均存在
备注:此次解析仅包含数据包Header部分,且不对“Reason String” 或者 “用户属性”解析进行代码上的合并优化。