W5500——MQTT协议介绍及源码分析

一、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涉及发送方和接收方之间的四部分握手。
QoS0

QoS1

QoS2

        在讨论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数据包结构如下:

  1. Fixed header——存在于所有类型数据包中,共占2Byte,其结构如下。

        1)其首字节的高四位为数据包报文类型,报文类型及其描述的列表如下。

报文类型功能描述传输方向value(bit7~4)
Reserved保留保留0000b(0d)
CONNECT客户机请求连接代理client to broker0001b(1d)
CONNACK连接请求的确认broker to client0010b(2d)
PUBLISH发布消息client to broker/broker to client0011b(3d)
PUBACK发布消息的确认client to broker/broker to client0100b(4d)
PUBREC发布的消息已接收client to broker/broker to client0101b(5d)
PUBREL发布的消息已释放client to broker/broker to client0110b(6d)
PUBCOMP发布完成client to broker/broker to client0111b(7d)
SUBSCRIBE订阅请求client to broker1000b(8d)
SUBACK订阅请求回应broker to client1001b(9d)
UNSUBSCRIBE取消订阅client to broker1010b(10d)
UNSUBACK取消订阅回应broker to client1011b(11d)
PINGREQping请求client to broker1100b(12d)
PINGRESPping请求回应broker to client1101b(13d)
DISCONNECT断开连接client to broker1110b(14d)
Reserved保留保留保留

        2)其首字节的低四位为数据包报文标识,报文标识列表如下。

报文类型标识bit3bit2bit1bit0
CONNECT保留0000
CONNACK保留0000
PUBLISHUsed in MQTT 3.1.1dupQoSretain
PUBACK保留0000
PUBREC保留0000
PUBREL保留0010
PUBCOMP保留0000
SUBSCRIBE保留0010
SUBACK保留0000
UNSUBSCRIBE保留0010
UNSUBACK保留0000
PINGREQ保留0000
PINGRESP保留0000
DISCONNECT保留0000
  •  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
CONNECTRequired
CONNACKnone
PUBLISHOptional
PUBACKnone
PUBRECnone
PUBRELnone
PUBCOMPnone
SUBSCRIBERequired
SUBACKRequired
UNSUBSCRIBERequired
UNSUBACKnone
PINGREQnone
PINGRESPnone
DISCONNECTnone

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.

connetct packet
BYTEDescriptionbit7bit6bit5bit4bit3bit2bit1bit0
Fixed headerpacket typeflags
100010000
remaining length
2...variable header size + payload size
Variable header
Protocol Name
1Length MSB(0)00000000
2Length LSB(4)00000100
3'M'01001101
4'Q'01010001
5'T'01010100
6'T'01010100
protocol level
7Level(4)00000100
Connect Flags
User Name FlagPassword FlagWill RetainWill QoSWill FlagClean SessionReserved
8xxxxxxx0
Keep Alive
9MSBxxxxxxxx
10LSBxxxxxxxx
Payload
Client Identifier
1Length MSB
2Length 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)
1Length MSB
2Length LSB
3...UTF-8 Encoded string Data for Will Topic
Will Message(If the Will Flag is set to 1)
1Length MSB
2Length LSB
3...UTF-8 Encoded string Data for Will Message
User Name(If the User Name Flag is set to 1)
1Length MSB
2Length LSB
3...UTF-8 Encoded string Data for User Name
Password(If the Password is set to 1)
1Length MSB
2Length 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。

connectACK packet
BYTEDescriptionbit7bit6bit5bit4bit3bit2bit1bit0
Fixed headerpacket typeflags
100100000
remaining length
200000010
Variable header
reservedSession Present Flag
1Connect Acknowledge Flags0000000x
2Connect Return codexxxxxxxx
PayloadCONNACK 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.

BYTEDescriptionbit7bit6bit5bit4bit3bit2bit1bit0
Fixed headerpacket typedupQoS levelRETAIN
10011xxxx
remaining length
2length of variable header plus the length of the payload
Variable header
Topic Name
1Length MSB
2Length LSB
3...UTF-8 Encoded string Data for Topic Name
Packet Identifier(if QoS is 1 or 2)
1Length MSB
2Length LSB
PayloadApplication Message

 1.8.4、PUBACK报文

        A PUBACK Packet is the response to a PUBLISH Packet with QoS level 1.

YTEDescriptionbit7bit6bit5bit4bit3bit2bit1bit0
Fixed headerpacket typeflags
101000000
remaining length
200000010
Variable header
Packet Identifier from the PUBLISH Packe
1MSBxxxxxxxx
2LSBxxxxxxxx
PayloadPUBACK 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格式是相同的。

BYTEDescriptionbit7bit6bit5bit4bit3bit2bit1bit0
Fixed headerpacket typeReserved
110000010
remaining length
2...length of variable header plus the length of the payload
Variable header
Packet Identifier
1MSBxxxxxxxx
2LSBxxxxxxxx
Payload
Topic1 Filter
1Length MSB
2Length LSB
3...NUTF-8 encoded strings for topic1
Requested QoS for topic1ReservedQoS
N+1000000xx
Topic2 Filter if need
N+2Length MSB
N+3Length LSB

1.8.9、SUBACK报文

BYTEDescriptionbit7bit6bit5bit4bit3bit2bit1bit0
Fixed headerpacket typeReserved
110010000
remaining length
2...length of variable header plus the length of the payload
Variable header
Packet Identifier
1MSBxxxxxxxx
2LSBxxxxxxxx
Payload
Return Code
1for topic1x00000xx
2for topic2 if needx00000xx

Allowed return codes:

        0x00 - Success - Maximum QoS0;

        0x01 - Success - Maximum QoS1;

        0x02 - Success - Maximum QoS2;

        0x80 - Failure;

1.8.10、UNSUBACK报文

BYTEDescriptionbit7bit6bit5bit4bit3bit2bit1bit0
Fixed headerpacket typeReserved
110110000
remaining length
200000010
Variable header
Packet Identifier
1MSBxxxxxxxx
2LSBxxxxxxxx
PayloadThe UNSUBACK Packet has no payload

1.8.11、PINGREQ报文

BYTEDescriptionbit7bit6bit5bit4bit3bit2bit1bit0
Fixed headerpacket typeReserved
111000000
remaining length
200000000
Variable headerThe PINGREQ Packet has no variable header.
PayloadThe PINGREQ Packet has no payload.

1.8.12、PINGRESP报文

BYTEDescriptionbit7bit6bit5bit4bit3bit2bit1bit0
Fixed headerpacket typeReserved
111010000
remaining length
200000000
Variable headerThe PINGRESP Packet has no variable header.
PayloadThe 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.

BYTEDescriptionbit7bit6bit5bit4bit3bit2bit1bit0
Fixed headerpacket typeReserved
111100000
remaining length
200000000
Variable headerThe DISCONNECT Packet has no variable header.
PayloadThe 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的源码内容介绍的差不多了,内容有点长了,下篇再写应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值