MQTT报文组成
MQTT报文由三部分组成,如下表所示。
表1.1 MQTT报文构成
1 | Fixed Header | 固定报头,所有报文必须包含,长度可变 |
---|---|---|
2 | Variable Header | 可变报头,不是所有报文都必须包含 |
3 | Payload | 报文负载,长度可变 |
Fixed Header
每一个MQTT报文都必须包含Fixed Header,其构成如表1.2所示
表1.2 Fixed Header组成
byte1 | bit7-bit4:MQTT报文类型 bit3-bit0:报文类型标志位 |
---|---|
byte2… | 剩余长度,整个MQTT报文出Fixed Header外的字节数 |
MQTT报文类型
如表1.3所示
表1.3 MQTT报文类型
报文类型 | 值 | 方向 | 描述 |
---|---|---|---|
Reserved | 0 | 禁止 | 保留 |
CONNECT | 1 | Client端到Broker端 | 链接请求 |
CONNACT | 2 | Broker端到Client端 | 链接请求响应 |
PUBLISH | 3 | 双向 | 消息发布 |
PUBACK | 4 | 双向 | QoS1等级消息接收确认 |
PUBREC | 5 | 双向 | QoS等级为2的消息的第一步,即对PUBLISH报文的响应 |
PUBREL | 6 | 双向 | QoS等级为2的消息的第二步,即对PUBREC报文的响应 |
PUBCOMP | 7 | 双向 | QoS等级为2的消息的第三步,即对PUBREL报文的响应,标志着一次QoS消息发布的完成 |
SUBSCRIBE | 8 | Client端到Broker端 | 消息订阅请求 |
SUBACK | 9 | Broker端到Client端 | 消息订阅请求响应 |
UNSUBSCRIBE | 10 | Client端到Broker端 | 消息订阅取消请求 |
UNSUBACK | 11 | Broker端到Client端 | 消息订阅取消请求响应 |
PINGREQ | 12 | Client端到Broker端 | 心跳请求 |
PINGRESP | 13 | Broker端到Client端 | 心跳请求响应 |
DISCONNECT | 14 | Client端到Broker端 | 链接断开请求 |
Reserved | 15 | 禁止 | 保留 |
报文类型标志位
控制报文 | 固定报头标志 | bit3 | bit2 | bit1 | bit0 |
---|---|---|---|---|---|
CONNECT | Reserved | 0 | 0 | 0 | 0 |
CONNACK | Reserved | 0 | 0 | 0 | 0 |
PUBLISH | Used in MQTT3.1.1 | DUP1:控制报文重复分发标志 | Qos2:PUBLISH报文QoS质量等级 | Qos2:PUBLISH报文QoS质量等级 | RETAIN:PUBLISH报文 |
PUBACK | Reserved | 0 | 0 | 0 | 0 |
PUBREC | Reserved | 0 | 0 | 0 | 0 |
PUBREL | Reserved | 0 | 0 | 0 | 0 |
PUBCOMP | Reserved | 0 | 0 | 0 | 0 |
SUBSCRIBE | Reserved | 0 | 0 | 0 | 0 |
SUBACK | Reserved | 0 | 0 | 0 | 0 |
UNSUBSCRIBE | Reserved | 0 | 0 | 0 | 0 |
UNSUBACK | Reserved | 0 | 0 | 0 | 0 |
PINGREQ | Reserved | 0 | 0 | 0 | 0 |
PINGRESP | Reserved | 0 | 0 | 0 | 0 |
DISCONNECT | Reserved | 0 | 0 | 0 | 0 |
剩余长度
剩余长度(Remaining Length)表示当前报文剩余部分的字节数,包括可变报头和负载的数据,不包括用于编码剩余长度字段本身的字节数。剩余长度字段使用一个变长度编码方案,对小于128的值它使用单字节编码。更大的值按下面的方式处理。低7位有效位用于编码数据,最高有效位用于指示是否有更多的字节。因此每个
字节可以编码128个数值和一个延续位(continuation bit)。剩余长度字段最大4个字节。
解码计算方法
int recvdlen = 0;
int multiplier = 1;
int remainlength = 0; //剩余长度
unsigned char byte = 0;
do{
recvdlen = read(client->socket, &byte, 1);
if(recvdlen != 1){
DEBUG_INFO("read data error \n");
return -1;
}
remainlength += (byte & 0x7F)*multiplier;
multiplier *= 0x80;
if(multiplier > 0x80*0x80*0x80){
DEBUG_INFO("calculate remain length error\n");
return -1;
}
}while((byte & 0x80) != 0);
编码计算方法
int x = 300; //x:remain length
unsigned char encodeByte = 0; //编码后的字节
do{
encodedByte = X % 128;
x = x / 128;
if(x > 0){
encodeByte = encodeByte | 128;
}
}while(x > 0);
Variable Header
某些MQTT控制报文在固定包头和负载之间,包含一个可变报头。可变报头的内容根据
报文类型的不同而不同。可变报头的报文标识符(Packet Identifier)字段存在于在多个类型
的报文里。
报文标识符Packet identifier
表2.1
byte1 | 报文标识符MLB |
---|---|
byte2 | 报文标识符SLB |
很多控制报文的可变报头部分包含一个两字节的报文标识符字段,这些报文包括PUBLISH(QoS > 0时), PUBACK,PUBREC,PUBREL,PUBCOMP,SUBSCRIBE,SUBACK,UNSUBSCIBE,UNSUBACK。
SUBSCRIBE,UNSUBSCRIBE和PUBLISH(QoS大于0)控制报文必须包含一个非零的16位报文标识符(Packet Identifier)。客户端每次发送一个新报文时都必须分配一个未使用的报文标识符 。如果一个客户端要重发这个特殊的控制报文,必须使用相同的标识符。当客户端处理完这个报文对应的确认后,报文标识符才可释放重用。
QoS 1的PUBLISH对应的是PUBACK,QoS 2的PUBLISH对应的是PUBCOMP,与SUBSCRIBE或UNSUBSCRIBE对应的分别是SUBACK或UNSUBACK 。PUBACK, PUBREC, PUBREL报文必须包含与最初发送的PUBLISH报文相同的报文标识符。同样,SUBACK和UNSUBACK必须包含SUBSCRIBE和UNSUBSCRIBE报文中使用的报文标识符。
有效载荷payload
某些MQTT控制报文在报文的最后部分包含一个有效载荷,将在每个报文的详细分析中具体描述。表格 3.1列出了具有有效载荷的控制报文。
表3.1
报文类型 | 有效载荷 |
---|---|
CONNECT | 需要 |
CONNACK | 不需要 |
PUBLISH | 可选 |
PUBACK | 不需要 |
PUBREC | 不需要 |
PUBREL | 不需要 |
PUBCOMP | 不需要 |
SUBSCRIBE | 需要 |
SUBACK | 需要 |
UNSUBSCRIBE | 需要 |
UNSUBACK | 不需要 |
PINGREQ | 不需要 |
PINGRESP | 不需要 |
DISCONNECT | 不需要 |
UTF-8编码
MQTT协议报文中的字符,使用UTF-8编码,后续对每个MQTT报文进行分析的文章会碰到。
UTF-8是针对Unicode的一种可变长度字符编码。它可以用来表示Unicode标准中的任何字符,而且其编码中的第一个字节仍与ASCII相容,使得原来处理ASCII字符的软件无须或只进行少部份修改后,便可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。
与ASCII相比,UTF-8在每个字符队列前加入了两个字节的长度字段,用来表示这个字符队列的长度(注意该字符队列不是字符串,不包括’\0’)。
二进制位 | 描述 |
---|---|
byte1 | 字符串长度MLB |
byte2 | 字符串长度SLB |
byte3… | 字符队列(ASCII编码) |
引用
1 MQTT协议中文版本》:https://mcxiaoke.gitbooks.io/mqtt-cn/content/
2 百度百科:https://baike.baidu.com/item/UTF-8/481798?fr=aladdin