一、MQTT简介
1.1、什么是mqtt
MQTT(Message Queuing Telemetry Transport, 消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年设计,并在2014年成为OASIS标准。详情参考官网介绍。
1.2、发布/订阅模式
MQTT客户机向MQTT代理发布消息,而其他MQTT客户机订阅它们想要接收的消息。MQTT代理则是作为发送方和接收方之间的中介,将消息分派给适当的接收方。而客户机作为消息发布者同时也可以是订阅者,可以订阅其它客户机发布的消息。
MQTT 传输的消息分为:主题(Topic)和消息的内容(payload)两部分。 Topic可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload)。 payload,可以理解为消息的内容,是指订阅者具体要使用的内容。
1.3、特点
- 轻量级和高效:MQTT将客户机所需的资源和网络带宽降至最低。
- 双向通信:MQTT促进了设备和服务器之间的通信,支持发布和订阅。它还允许向设备组广播消息。
- 可扩展性:MQTT可以扩展到支持物联网或工业物联网生态系统中的数百万个设备或“事物”。
- 服务质量(QoS)级别:MQTT指定不同的QoS级别,以确保可靠的消息传递。
- 持久会话:MQTT支持设备和服务器之间的持久会话,减少了不可靠网络上的重新连接时间。
- 安全特性:MQTT支持用于消息机密性的TLS加密和用于客户端验证的身份验证协议。
1.4、服务质量等级QoS(Quality of Service)
MQTT代理还可以为断开连接的客户机缓冲消息,确保即使在不可靠的网络条件下也能可靠地传递消息。为了实现这一点,MQTT支持三种不同的QoS(Quality of Service)等级:0(最多传输一次)、1(至少传输一次)和2(只传输一次)。
- QoS 0:消息最多传递一次,通常被称为“即发即弃”。其中发送方不期望消息传递得到确认或保证。这意味着接收方不承认收到了消息,发送方也不存储或重新传输消息。
- QoS 1:重点是确保消息至少向接收方传递一次。当以QoS 1发布消息时,发送方保留该消息的副本,直到收到来自接收方的PUBACK包,确认成功接收。如果发送方在合理的时间范围内没有收到PUBACK报文,则重新发送该报文以确保其送达。
- QoS 2:提供了MQTT中最高级别的服务,确保每条消息只向预期的接收方传递一次。为了实现这一点,QoS 2涉及发送方和接收方之间的四部分握手。



在讨论MQTT中的QoS时,重要的是要考虑从发布客户机到代理以及从代理到订阅客户机的消息传递。消息传递的这两个方面有细微的差别。向代理发布消息的客户端在传输期间为消息定义QoS级别。然后,代理使用每个订阅客户端在订阅过程中定义的QoS级别将消息传输到订阅客户端。如果订阅客户端定义的QoS级别低于发布客户端,则代理将使用较低的QoS级别传输消息。
1.5、遗嘱消息LWT( Last Will and Testament)
最后的遗嘱(Last Will and Testament, LWT)是MQTT中的一个强大功能,它允许客户端指定一条消息,当发生意外断开连接时,broker将代表客户端自动发布该消息。它提供了一种可靠的通信方式,并确保客户端可以优雅地处理断开连接,而不会使主题处于不一致的状态。
该消息遵循常规MQTT消息结构的结构,包括主题、保留的消息标志、服务质量(QoS)和有效负载。
根据MQTT 3.1.1规范,代理在以下情况下发送客户端的最后遗嘱(Last Will and Testament, LWT)消息:
- I/O错误或网络故障:如果代理检测到输入/输出或网络连接有任何问题,它将分发LWT消息。
- Keep Alive时间内通信失败:如果在指定的Keep Alive时间内客户端与代理通信失败,则发送LWT消息。在MQTT Essentials的第10部分中,我们将探讨MQTT Keep Alive时间的概念并深入研究它的意义。
- 客户端关闭连接而不断开连接:当客户端终止网络连接而不发送断开包时,代理确保分发LWT消息。
- 代理由于协议错误而关闭连接:如果代理由于协议错误而关闭网络连接,它将发送LWT消息。
1.6、Keep Alive
MQTT中的Keep Alive机制确保了连接的活动性,并为代理提供了一种检测客户端是否无响应或断开连接的方法。当客户机与MQTT代理建立连接时,它协商一个Keep Alive值,该值是以秒为单位表示的时间间隔。客户端必须在此间隔内至少向代理发送一次PINGREQ数据包,以表明其存在并保持连接存活。在接收到PINGREQ数据包后,代理使用PINGRESP数据包进行响应,确认连接仍处于活动状态。最大Keep Alive间隔为18小时12分15秒。反之,将Keep Alive间隔设置为0可以有效地禁用Keep Alive机制。
如果客户机在Keep Alive间隔的1.5倍内未能发送任何消息或PINGREQ数据包,则代理负责断开客户机的连接。同样,如果客户端在合理的时间范围内没有收到代理的响应,则应关闭连接。如果代理在预期的时间范围内没有收到来自客户机的PINGREQ数据包或任何其他数据包,则代理将关闭连接并发送最后遗嘱消息(LWT)消息(如果客户机指定了LWT消息)。
1.7、协议数据包
以下解释参考官方V3.1.1版本。
1.7.1、control packet
在MQTT协议中,一个MQTT数据包由: 固定头(Fixed header)、可变头(Variable header)、消息体(payload)三部分构成。MQTT数据包结构如下:
- Fixed header——存在于所有类型数据包中,共占2Byte,其结构如下。
1)其首字节的高四位为数据包报文类型,报文类型及其描述的列表如下。
报文类型 | 功能描述 | 传输方向 | value(bit7~4) |
Reserved | 保留 | 保留 | 0000b(0d) |
CONNECT | 客户机请求连接代理 | client to broker | 0001b(1d) |
CONNACK | 连接请求的确认 | broker to client | 0010b(2d) |
PUBLISH | 发布消息 | client to broker/broker to client | 0011b(3d) |
PUBACK | 发布消息的确认 | client to broker/broker to client | 0100b(4d) |
PUBREC | 发布的消息已接收 | client to broker/broker to client | 0101b(5d) |
PUBREL | 发布的消息已释放 | client to broker/broker to client | 0110b(6d) |
PUBCOMP | 发布完成 | client to broker/broker to client | 0111b(7d) |
SUBSCRIBE | 订阅请求 | client to broker | 1000b(8d) |
SUBACK | 订阅请求回应 | broker to client | 1001b(9d) |
UNSUBSCRIBE | 取消订阅 | client to broker | 1010b(10d) |
UNSUBACK | 取消订阅回应 | broker to client | 1011b(11d) |
PINGREQ | ping请求 | client to broker | 1100b(12d) |
PINGRESP | ping请求回应 | broker to client | 1101b(13d) |
DISCONNECT | 断开连接 | client to broker | 1110b(14d) |
Reserved | 保留 | 保留 | 保留 |
2)其首字节的低四位为数据包报文标识,报文标识列表如下。
报文类型 | 标识 | bit3 | bit2 | bit1 | bit0 |
CONNECT | 保留 | 0 | 0 | 0 | 0 |
CONNACK | 保留 | 0 | 0 | 0 | 0 |
PUBLISH | Used in MQTT 3.1.1 | dup | QoS | retain | |
PUBACK | 保留 | 0 | 0 | 0 | 0 |
PUBREC | 保留 | 0 | 0 | 0 | 0 |
PUBREL | 保留 | 0 | 0 | 1 | 0 |
PUBCOMP | 保留 | 0 | 0 | 0 | 0 |
SUBSCRIBE | 保留 | 0 | 0 | 1 | 0 |
SUBACK | 保留 | 0 | 0 | 0 | 0 |
UNSUBSCRIBE | 保留 | 0 | 0 | 1 | 0 |
UNSUBACK | 保留 | 0 | 0 | 0 | 0 |
PINGREQ | 保留 | 0 | 0 | 0 | 0 |
PINGRESP | 保留 | 0 | 0 | 0 | 0 |
DISCONNECT | 保留 | 0 | 0 | 0 | 0 |
- dup,如果该值为1,表明这个数据包是一条重复的消息;否则该数据包就是第一次发布的消息;对于所有QoS为0的消息,DUP标志必须设置为0;
- QoS,参考1.4章节;
- retain,如果RETAIN标志设置为1,在客户端发送给服务器的PUBLISH包中,服务器必须存储
应用程序消息及其QoS,以便将其传递给未来的订阅者。如果RETAIN标志为0,服务器绝对不能存储、删除或替换任何现有的保留消息。
3)Remaining Length表示该条数据包剩余字节长度(包含variable header and the payload两部分),Remaining Length从第二个字节开始,最多为4字节。remianning length编码时每个字节只用低7位,最高位用来表示该字节后面还有数据。这个比较绕,我也看了一会,举个栗子就明白了。
比如我数据包长度是小于128(variable header and the payload两部分),那么remianning length就占一个字节;如果数据包长度(value)大于或等于128且小于16384,那么remianning length就占两个字节。这个两个字节具体怎么算呢,答案是第一个字节为长度对128取余再加上128(value/128+128),第二个字节为长度对128取整(value%128)
1.7.2、variable header
可变头——顾名思义在数据包报文里不是必须的,根据报文类型的不同可变头部分包含的字段也不同,这取决与fixed header里面的报文类型。下面列举一下可变头部分可能会添加的字段。
- Packet Identifier(2byte):PUBLISH (where QoS > 0), PUBACK, PUBREC, PUBREL, PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK.这些交互类的报文类型需要包含Packet Identifier,主要就是确保正确有效地传递消息;在PUBLISH 包中,如果QoS 是 0 的 ,则不需要携带 Packet Id,因为不需要应答(connect报文只会发送一次)。此时 Packet Id 需要被设置为 0就行;PUBACK, PUBREC, PUBREL, PUBCOMP必须和PUBLISH中的Packet Id保持一致,同样在SUBSCRIBE, SUBACK中,在UNSUBSCRIBE, UNSUBACK中也需要保持一致;服务器和客户端在选择 Packet Id 时,是相互独立的,比如是有可能两边发送的PUBLISH包都包含相同的Packet Id;客户端每次发送一个新包必须使用一个从未使用的Packet Id,如果客户端已经接收到前一个包ACK响应之后可以重用之前的Packet Id,另外如果客户端重发包的时候,需要保证这个包和之前发送的包使用相同的Packet Id;
- Protocol Name(6byte):在CONNECT报文中,前两字节表示数据长度为4,后四字节为UTF编码的字符串“MQTT”;
- Topic Name:在publish报文里面,在variable header中需要包含要发布的topic的名字,包含两字节的长度和不定长的名字;
- Protocol Level:只在CONNECT报文中包含,占一个字节。指示客户端使用的协议版本。如果不是服务器期望的版本,应在CONNACK报文中赋错误码0x01;
- Connect Flags:只在CONNECT报文中包含,占一个字节;
- Keep Alive:只在CONNECT报文中包含,占两个字节,具体参考1.6章节;
1.7.3、Payload
MQTT不是所有报文类型都包含Payload的,且每种报文payload包含的内容也有所不同。如PUBLISH的Payload就是指消息内容(应用程序发布的消息内容)。而CONNECT的Payload则包含Client Identifier, Will Topic, Will Message, Username, Password等信息,关于报文类型是否包含payload的列表如下。
报文类型 | 是否包含payload |
CONNECT | Required |
CONNACK | none |
PUBLISH | Optional |
PUBACK | none |
PUBREC | none |
PUBREL | none |
PUBCOMP | none |
SUBSCRIBE | Required |
SUBACK | Required |
UNSUBSCRIBE | Required |
UNSUBACK | none |
PINGREQ | none |
PINGRESP | none |
DISCONNECT | none |
1.8、报文类型
1.8.1、CONNECT报文
the first Packet sent from the Client to the Server MUST be a CONNECT Packe,A Client can only send the CONNECT Packet once over a Network Connection.

BYTE | Description | bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 |
Fixed header | packet type | flags | |||||||
1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | |
remaining length | |||||||||
2... | variable header size + payload size | ||||||||
Variable header | |||||||||
Protocol Name | |||||||||
1 | Length MSB(0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | Length LSB(4) | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
3 | 'M' | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
4 | 'Q' | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 |
5 | 'T' | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
6 | 'T' | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 |
protocol level | |||||||||
7 | Level(4) | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
Connect Flags | |||||||||
User Name Flag | Password Flag | Will Retain | Will QoS | Will Flag | Clean Session | Reserved | |||
8 | x | x | x | x | x | x | x | 0 | |
Keep Alive | |||||||||
9 | MSB | x | x | x | x | x | x | x | x |
10 | LSB | x | x | x | x | x | x | x | x |
Payload | |||||||||
Client Identifier | |||||||||
1 | Length MSB | ||||||||
2 | Length LSB | ||||||||
3... | UTF-8 Encoded string for Client Identifier, The Server MUST allow ClientIds which are between 1 and 23 | ||||||||
Will Topic(If the Will Flag is set to 1) | |||||||||
1 | Length MSB | ||||||||
2 | Length LSB | ||||||||
3... | UTF-8 Encoded string Data for Will Topic | ||||||||
Will Message(If the Will Flag is set to 1) | |||||||||
1 | Length MSB | ||||||||
2 | Length LSB | ||||||||
3... | UTF-8 Encoded string Data for Will Message | ||||||||
User Name(If the User Name Flag is set to 1) | |||||||||
1 | Length MSB | ||||||||
2 | Length LSB | ||||||||
3... | UTF-8 Encoded string Data for User Name | ||||||||
Password(If the Password is set to 1) | |||||||||
1 | Length MSB | ||||||||
2 | Length LSB | ||||||||
3... | UTF-8 Encoded string Data for Password |
1.8.2、CONNACK报文
The CONNACK Packet is the packet sent by the Server in response to a CONNECT Packet received from a Client. The first packet sent from the Server to the Client MUST be a CONNACK Packet。

BYTE | Description | bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 |
Fixed header | packet type | flags | |||||||
1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | |
remaining length | |||||||||
2 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | |
Variable header | |||||||||
reserved | Session Present Flag | ||||||||
1 | Connect Acknowledge Flags | 0 | 0 | 0 | 0 | 0 | 0 | 0 | x |
2 | Connect Return code | x | x | x | x | x | x | x | x |
Payload | CONNACK Packet has no payload |
1.8.3、PUBLISH报文
A PUBLISH Control Packet is sent from a Client to a Server or from Server to a Client to transport an Application Message.
BYTE | Description | bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 |
Fixed header | packet type | dup | QoS level | RETAIN | |||||
1 | 0 | 0 | 1 | 1 | x | x | x | x | |
remaining length | |||||||||
2 | length of variable header plus the length of the payload | ||||||||
Variable header | |||||||||
Topic Name | |||||||||
1 | Length MSB | ||||||||
2 | Length LSB | ||||||||
3... | UTF-8 Encoded string Data for Topic Name | ||||||||
Packet Identifier(if QoS is 1 or 2) | |||||||||
1 | Length MSB | ||||||||
2 | Length LSB | ||||||||
Payload | Application Message |
1.8.4、PUBACK报文
A PUBACK Packet is the response to a PUBLISH Packet with QoS level 1.
YTE | Description | bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 |
Fixed header | packet type | flags | |||||||
1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | |
remaining length | |||||||||
2 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | |
Variable header | |||||||||
Packet Identifier from the PUBLISH Packe | |||||||||
1 | MSB | x | x | x | x | x | x | x | x |
2 | LSB | x | x | x | x | x | x | x | x |
Payload | PUBACK Packet has no payload |
1.8.5、PUBREC报文
A PUBREC Packet is the response to a PUBLISH Packet with QoS 2. It is the second packet of the QoS 2 protocol exchange.
PUBREC报文和PUBACK报文类似,参考1.8.4章节。唯一区别就是packet type不一样,这里就不细述。
1.8.6、PUBREL报文
A PUBREL Packet is the response to a PUBREC Packet. It is the third packet of the QoS 2 protocol exchange.
PUBREL 报文和PUBACK报文类似,参考1.8.4章节。唯一区别就是packet type不一样,这里同样不细述。
1.8.7、PUBCOMP报文
The PUBCOMP Packet is the response to a PUBREL Packet. It is the fourth and final packet of the QoS 2 protocol exchange.
PUBCOMP报文和PUBACK报文类似,参考1.8.4章节。唯一区别就是packet type不一样,这里同样不细述。
1.8.8、SUBSCRIBE和UNSUBSCRIBE报文
The payload of a SUBSCRIBE Packet contains a list of Topic Filters indicating the Topics to which the Client wants to subscribe.
An UNSUBSCRIBE Packet is sent by the Client to the Server, to unsubscribe from topics.
SUBSCRIBE和UNSUBSCRIBE报文协议内容除了packet type格式是相同的。
BYTE | Description | bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 |
Fixed header | packet type | Reserved | |||||||
1 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | |
remaining length | |||||||||
2... | length of variable header plus the length of the payload | ||||||||
Variable header | |||||||||
Packet Identifier | |||||||||
1 | MSB | x | x | x | x | x | x | x | x |
2 | LSB | x | x | x | x | x | x | x | x |
Payload | |||||||||
Topic1 Filter | |||||||||
1 | Length MSB | ||||||||
2 | Length LSB | ||||||||
3...N | UTF-8 encoded strings for topic1 | ||||||||
Requested QoS for topic1 | Reserved | QoS | |||||||
N+1 | 0 | 0 | 0 | 0 | 0 | 0 | x | x | |
Topic2 Filter if need | |||||||||
N+2 | Length MSB | ||||||||
N+3 | Length LSB |
1.8.9、SUBACK报文
BYTE | Description | bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 |
Fixed header | packet type | Reserved | |||||||
1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | |
remaining length | |||||||||
2... | length of variable header plus the length of the payload | ||||||||
Variable header | |||||||||
Packet Identifier | |||||||||
1 | MSB | x | x | x | x | x | x | x | x |
2 | LSB | x | x | x | x | x | x | x | x |
Payload | |||||||||
Return Code | |||||||||
1 | for topic1 | x | 0 | 0 | 0 | 0 | 0 | x | x |
2 | for topic2 if need | x | 0 | 0 | 0 | 0 | 0 | x | x |
Allowed return codes:
0x00 - Success - Maximum QoS0;
0x01 - Success - Maximum QoS1;
0x02 - Success - Maximum QoS2;
0x80 - Failure;
1.8.10、UNSUBACK报文
BYTE | Description | bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 |
Fixed header | packet type | Reserved | |||||||
1 | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | |
remaining length | |||||||||
2 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | |
Variable header | |||||||||
Packet Identifier | |||||||||
1 | MSB | x | x | x | x | x | x | x | x |
2 | LSB | x | x | x | x | x | x | x | x |
Payload | The UNSUBACK Packet has no payload |
1.8.11、PINGREQ报文
BYTE | Description | bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 |
Fixed header | packet type | Reserved | |||||||
1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | |
remaining length | |||||||||
2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
Variable header | The PINGREQ Packet has no variable header. | ||||||||
Payload | The PINGREQ Packet has no payload. |
1.8.12、PINGRESP报文
BYTE | Description | bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 |
Fixed header | packet type | Reserved | |||||||
1 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | |
remaining length | |||||||||
2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
Variable header | The PINGRESP Packet has no variable header. | ||||||||
Payload | The PINGRESP Packet has no payload. |
1.8.13、DISCONNECT报文
The DISCONNECT Packet is the final Control Packet sent from the Client to the Server. It indicates that 1140 the Client is disconnecting cleanly.
BYTE | Description | bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 |
Fixed header | packet type | Reserved | |||||||
1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | |
remaining length | |||||||||
2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
Variable header | The DISCONNECT Packet has no variable header. | ||||||||
Payload | The DISCONNECT Packet has no payload. |
二、W5500——MQTT源码
2.1、CONNECT
结合1.8.1章节先看下connect报文相关的内容,后面需要用到。connect报文相关的结构体定义如下,在MQTTConnect.h文件中。
typedef struct
{
/** The eyecatcher for this structure. must be MQTC. */
char struct_id[4];
/** The version number of this structure. Must be 0 */
int struct_version;
/** Version of MQTT to be used. 3 = 3.1 4 = 3.1.1
*/
unsigned char MQTTVersion;
MQTTString clientID;
unsigned short keepAliveInterval;
unsigned char cleansession;
unsigned char willFlag;
MQTTPacket_willOptions will;
MQTTString username;
MQTTString password;
} MQTTPacket_connectData;
2.1.1、CONNECT组包
CONNECT报文是客户机发给服务器的,组包相关代码在MQTTConnectClient.c文件中。
如下图所示,在MQTTSerialize_connectLength函数中根据配置的MQTTVersion定义Variable header的长度10或者12,区别就是多两个字符串。
再下面就是计算payload段的报文长度,在connect报文中,Client Identifier是需要的。计算出Client Identifier的长度后再根据Connect Flags字段中的配置(入参,也就是上文中结构体),确认是否添加will.topicName、options->will.message、username和password;
int MQTTSerialize_connectLength(MQTTPacket_connectData* options)
{
int len = 0;
FUNC_ENTRY;
if (options->MQTTVersion == 3)
len = 12; /* variable depending on MQTT or MQIsdp */
else if (options->MQTTVersion == 4)
len = 10;
len += MQTTstrlen(options->clientID)+2;
if (options->willFlag)
len += MQTTstrlen(options->will.topicName)+2 + MQTTstrlen(options->will.message)+2;
if (options->username.cstring || options->username.lenstring.data)
len += MQTTstrlen(options->username)+2;
if (options->password.cstring || options->password.lenstring.data)
len += MQTTstrlen(options->password)+2;
FUNC_EXIT_RC(len);
return len;
}
如下所示,在MQTTSerialize_connect函数中,先通过上文介绍的MQTTSerialize_connectLength计算出Variable header+payload段报文长度后,通过MQTTPacket_len函数再计算出整条报文的长度;
MQTTHeader是一个联合体占一个字节,表示就是Fixed header部分的第一个字节,先写入发送缓存;
MQTTPacket_encode根据前面计算的总报文长度,计算出Remaining Length的值,Remaining Length的计算参考1.7.1章节;
再接下来就是Variable header段的填充。首先是Protocol Name,写入‘MQTT’即可;然后是Protocol Level填入4;MQTTConnectFlags也是一个联合体,占一个字节,对应协议中的Connect Flags字段;Variable header段最后再填入两个字节的Keep Alive就结束了;
最后就是payload段的填充,首先添加必要的Client Identifier,再根据Connect Flags配置添加相应数据即可。
至此,connect报文的组包就完成了。
/**
* Serializes the connect options into the buffer.
* @param buf the buffer into which the packet will be serialized
* @param len the length in bytes of the supplied buffer
* @param options the options to be used to build the connect packet
* @return serialized length, or error if 0
*/
int MQTTSerialize_connect(unsigned char* buf, int buflen, MQTTPacket_connectData* options)
{
unsigned char *ptr = buf;
MQTTHeader header = {0};
MQTTConnectFlags flags = {0};
int len = 0;
int rc = -1;
FUNC_ENTRY;
if (MQTTPacket_len(len = MQTTSerialize_connectLength(options)) > buflen)
{
rc = MQTTPACKET_BUFFER_TOO_SHORT;
goto exit;
}
header.byte = 0;
header.bits.type = CONNECT;
writeChar(&ptr, header.byte); /* write header */
ptr += MQTTPacket_encode(ptr, len); /* write remaining length */
if (options->MQTTVersion == 4)
{
writeCString(&ptr, "MQTT");
writeChar(&ptr, (char) 4);//Protocol Level
}
else
{
writeCString(&ptr, "MQIsdp");
writeChar(&ptr, (char) 3);
}
flags.all = 0;
flags.bits.cleansession = options->cleansession;
flags.bits.will = (options->willFlag) ? 1 : 0;
if (flags.bits.will)
{
flags.bits.willQoS = options->will.qos;
flags.bits.willRetain = options->will.retained;
}
if (options->username.cstring || options->username.lenstring.data)
flags.bits.username = 1;
if (options->password.cstring || options->password.lenstring.data)
flags.bits.password = 1;
writeChar(&ptr, flags.all);
writeInt(&ptr, options->keepAliveInterval);
writeMQTTString(&ptr, options->clientID);
if (options->willFlag)
{
writeMQTTString(&ptr, options->will.topicName);
writeMQTTString(&ptr, options->will.message);
}
if (flags.bits.username)
writeMQTTString(&ptr, options->username);
if (flags.bits.password)
writeMQTTString(&ptr, options->password);
rc = ptr - buf;
exit: FUNC_EXIT_RC(rc);
return rc;
}
2.1.2、CONNECT解包
解包相关内容在MQTTConnectServer.c文件中,这里不做细述了,参考2.1.1章节理解起来应该比较简单。
2.2、CONNACK
2.2.1、CONNACK解包
CONNACK报文是服务器发给客户机的,所以解包相关代码也在MQTTConnectClient.c文件中。
CONNACK报文相对简单,参考1.8.2章节,主要就是解析Connect Acknowledge Flags的最低位及Connect Return code。Return code为0时表示连接成功,非零值错误信息参考文档。
/**
* Deserializes the supplied (wire) buffer into connack data - return code
* @param sessionPresent the session present flag returned (only for MQTT 3.1.1)
* @param connack_rc returned integer value of the connack return code
* @param buf the raw buffer data, of the correct length determined by the remaining length field
* @param len the length in bytes of the data in the supplied buffer
* @return error code. 1 is success, 0 is failure
*/
int MQTTDeserialize_connack(unsigned char* sessionPresent, unsigned char* connack_rc, unsigned char* buf, int buflen)
{
MQTTHeader header = {0};
unsigned char* curdata = buf;
unsigned char* enddata = NULL;
int rc = 0;
int mylen;
MQTTConnackFlags flags = {0};
FUNC_ENTRY;
header.byte = readChar(&curdata);
if (header.bits.type != CONNACK)
goto exit;
curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */
enddata = curdata + mylen;
if (enddata - curdata < 2)
goto exit;
flags.all = readChar(&curdata);
*sessionPresent = flags.bits.sessionpresent;
*connack_rc = readChar(&curdata);
rc = 1;
exit:
FUNC_EXIT_RC(rc);
return rc;
}
2.2.2、CONNACK组包
组包相关内容在MQTTConnectServer.c文件中(MQTTSerialize_connack),这里不做细述了。
2.3、PUBLISH
2.3.1、PUBLISH组包
publish组包在MQTTSerializePublish.c文件中,到了这里起始只要了解了PUBLISH包组成,看代码就已经很简单了。需要注意 的就是packetid是否需要是由QoS的值决定的。
/**
* Serializes the supplied publish data into the supplied buffer, ready for sending
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer
* @param dup integer - the MQTT dup flag
* @param qos integer - the MQTT QoS value
* @param retained integer - the MQTT retained flag
* @param packetid integer - the MQTT packet identifier
* @param topicName MQTTString - the MQTT topic in the publish
* @param payload byte buffer - the MQTT publish payload
* @param payloadlen integer - the length of the MQTT payload
* @return the length of the serialized data. <= 0 indicates error
*/
int MQTTSerialize_publish(unsigned char* buf, int buflen, unsigned char dup, int qos, unsigned char retained, unsigned short packetid,
MQTTString topicName, unsigned char* payload, int payloadlen)
{
unsigned char *ptr = buf;
MQTTHeader header = {0};
int rem_len = 0;
int rc = 0;
FUNC_ENTRY;
if (MQTTPacket_len(rem_len = MQTTSerialize_publishLength(qos, topicName, payloadlen)) > buflen)
{
rc = MQTTPACKET_BUFFER_TOO_SHORT;
goto exit;
}
header.bits.type = PUBLISH;
header.bits.dup = dup;
header.bits.qos = qos;
header.bits.retain = retained;
writeChar(&ptr, header.byte); /* write header */
ptr += MQTTPacket_encode(ptr, rem_len); /* write remaining length */;
writeMQTTString(&ptr, topicName);
if (qos > 0)
writeInt(&ptr, packetid);
memcpy(ptr, payload, payloadlen);
ptr += payloadlen;
rc = ptr - buf;
exit:
FUNC_EXIT_RC(rc);
return rc;
}
2.3.2、PUBLISH解包
在MQTTDeserializePublish.c文件中
/**
* Deserializes the supplied (wire) buffer into publish data
* @param dup returned integer - the MQTT dup flag
* @param qos returned integer - the MQTT QoS value
* @param retained returned integer - the MQTT retained flag
* @param packetid returned integer - the MQTT packet identifier
* @param topicName returned MQTTString - the MQTT topic in the publish
* @param payload returned byte buffer - the MQTT publish payload
* @param payloadlen returned integer - the length of the MQTT payload
* @param buf the raw buffer data, of the correct length determined by the remaining length field
* @param buflen the length in bytes of the data in the supplied buffer
* @return error code. 1 is success
*/
int MQTTDeserialize_publish(unsigned char* dup, int* qos, unsigned char* retained, unsigned short* packetid, MQTTString* topicName,
unsigned char** payload, int* payloadlen, unsigned char* buf, int buflen)
{
MQTTHeader header = {0};
unsigned char* curdata = buf;
unsigned char* enddata = NULL;
int rc = 0;
int mylen = 0;
FUNC_ENTRY;
header.byte = readChar(&curdata);
if (header.bits.type != PUBLISH)
goto exit;
*dup = header.bits.dup;
*qos = header.bits.qos;
*retained = header.bits.retain;
curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */
enddata = curdata + mylen;
if (!readMQTTLenString(topicName, &curdata, enddata) ||
enddata - curdata < 0) /* do we have enough data to read the protocol version byte? */
goto exit;
if (*qos > 0)
*packetid = readInt(&curdata);
*payloadlen = enddata - curdata;
*payload = curdata;
rc = 1;
exit:
FUNC_EXIT_RC(rc);
return rc;
}
2.4、PUBACK、PUBREL、PUBCOMP
2.4.1、组包
/**
* Serializes the ack packet into the supplied buffer.
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer
* @param type the MQTT packet type
* @param dup the MQTT dup flag
* @param packetid the MQTT packet identifier
* @return serialized length, or error if 0
*/
int MQTTSerialize_ack(unsigned char* buf, int buflen, unsigned char packettype, unsigned char dup, unsigned short packetid)
{
MQTTHeader header = {0};
int rc = 0;
unsigned char *ptr = buf;
FUNC_ENTRY;
if (buflen < 4)
{
rc = MQTTPACKET_BUFFER_TOO_SHORT;
goto exit;
}
header.bits.type = packettype;
header.bits.dup = dup;
header.bits.qos = (packettype == PUBREL) ? 1 : 0;
writeChar(&ptr, header.byte); /* write header */
ptr += MQTTPacket_encode(ptr, 2); /* write remaining length */
writeInt(&ptr, packetid);
rc = ptr - buf;
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Serializes a puback packet into the supplied buffer.
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer
* @param packetid integer - the MQTT packet identifier
* @return serialized length, or error if 0
*/
int MQTTSerialize_puback(unsigned char* buf, int buflen, unsigned short packetid)
{
return MQTTSerialize_ack(buf, buflen, PUBACK, 0, packetid);
}
/**
* Serializes a pubrel packet into the supplied buffer.
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer
* @param dup integer - the MQTT dup flag
* @param packetid integer - the MQTT packet identifier
* @return serialized length, or error if 0
*/
int MQTTSerialize_pubrel(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid)
{
return MQTTSerialize_ack(buf, buflen, PUBREL, dup, packetid);
}
/**
* Serializes a pubrel packet into the supplied buffer.
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer
* @param packetid integer - the MQTT packet identifier
* @return serialized length, or error if 0
*/
int MQTTSerialize_pubcomp(unsigned char* buf, int buflen, unsigned short packetid)
{
return MQTTSerialize_ack(buf, buflen, PUBCOMP, 0, packetid);
}
2.5、SUBSCRIBE
2.5.1、SUBSCRIBE组包
在MQTTSubscribeClient.c文件中
/**
* Serializes the supplied subscribe data into the supplied buffer, ready for sending
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied bufferr
* @param dup integer - the MQTT dup flag
* @param packetid integer - the MQTT packet identifier
* @param count - number of members in the topicFilters and reqQos arrays
* @param topicFilters - array of topic filter names
* @param requestedQoSs - array of requested QoS
* @return the length of the serialized data. <= 0 indicates error
*/
int MQTTSerialize_subscribe(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid, int count,
MQTTString topicFilters[], int requestedQoSs[])
{
unsigned char *ptr = buf;
MQTTHeader header = {0};
int rem_len = 0;
int rc = 0;
int i = 0;
FUNC_ENTRY;
if (MQTTPacket_len(rem_len = MQTTSerialize_subscribeLength(count, topicFilters)) > buflen)
{
rc = MQTTPACKET_BUFFER_TOO_SHORT;
goto exit;
}
header.byte = 0;
header.bits.type = SUBSCRIBE;
header.bits.dup = dup;
header.bits.qos = 1;
writeChar(&ptr, header.byte); /* write header */
ptr += MQTTPacket_encode(ptr, rem_len); /* write remaining length */;
writeInt(&ptr, packetid);
for (i = 0; i < count; ++i)
{
writeMQTTString(&ptr, topicFilters[i]);
writeChar(&ptr, requestedQoSs[i]);
}
rc = ptr - buf;
exit:
FUNC_EXIT_RC(rc);
return rc;
}
2.6、UNSUBSCRIBE
2.6.1、UNSUBSCRIBE组包
在MQTTUnsubscribeClient.c文件中
/**
* Serializes the supplied unsubscribe data into the supplied buffer, ready for sending
* @param buf the raw buffer data, of the correct length determined by the remaining length field
* @param buflen the length in bytes of the data in the supplied buffer
* @param dup integer - the MQTT dup flag
* @param packetid integer - the MQTT packet identifier
* @param count - number of members in the topicFilters array
* @param topicFilters - array of topic filter names
* @return the length of the serialized data. <= 0 indicates error
*/
int MQTTSerialize_unsubscribe(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid,
int count, MQTTString topicFilters[])
{
unsigned char *ptr = buf;
MQTTHeader header = {0};
int rem_len = 0;
int rc = -1;
int i = 0;
FUNC_ENTRY;
if (MQTTPacket_len(rem_len = MQTTSerialize_unsubscribeLength(count, topicFilters)) > buflen)
{
rc = MQTTPACKET_BUFFER_TOO_SHORT;
goto exit;
}
header.byte = 0;
header.bits.type = UNSUBSCRIBE;
header.bits.dup = dup;
header.bits.qos = 1;
writeChar(&ptr, header.byte); /* write header */
ptr += MQTTPacket_encode(ptr, rem_len); /* write remaining length */;
writeInt(&ptr, packetid);
for (i = 0; i < count; ++i)
writeMQTTString(&ptr, topicFilters[i]);
rc = ptr - buf;
exit:
FUNC_EXIT_RC(rc);
return rc;
}
2.7、PINGREQ和DISCONNECT
2.7.1、组包
在MQTTConnectClient.c文件中
/**
* Serializes a 0-length packet into the supplied buffer, ready for writing to a socket
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer, to avoid overruns
* @param packettype the message type
* @return serialized length, or error if 0
*/
int MQTTSerialize_zero(unsigned char* buf, int buflen, unsigned char packettype)
{
MQTTHeader header = {0};
int rc = -1;
unsigned char *ptr = buf;
FUNC_ENTRY;
if (buflen < 2)
{
rc = MQTTPACKET_BUFFER_TOO_SHORT;
goto exit;
}
header.byte = 0;
header.bits.type = packettype;
writeChar(&ptr, header.byte); /* write header */
ptr += MQTTPacket_encode(ptr, 0); /* write remaining length */
rc = ptr - buf;
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Serializes a disconnect packet into the supplied buffer, ready for writing to a socket
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer, to avoid overruns
* @return serialized length, or error if 0
*/
int MQTTSerialize_disconnect(unsigned char* buf, int buflen)
{
return MQTTSerialize_zero(buf, buflen, DISCONNECT);
}
/**
* Serializes a disconnect packet into the supplied buffer, ready for writing to a socket
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer, to avoid overruns
* @return serialized length, or error if 0
*/
int MQTTSerialize_pingreq(unsigned char* buf, int buflen)
{
return MQTTSerialize_zero(buf, buflen, PINGREQ);
}
3、结语
至此,关于MQTT协议以及结合w5500中mqtt的源码内容介绍的差不多了,内容有点长了,下篇再写应用。