MQTT协议分析总结(一)

相关文章

1.《MQTT协议分析总结(一)》
2.《MQTT协议分析总结(二)》
3.《【IoT】如何连接到百度IoT云》
4.《【FreeRTOS】基于STM32移植LWIP 2.1.2之MQTT》

1.前言

MQTT 3 (当前版本3.1.1)是目前使用的最为广泛的MQTT协议标准。尽管MQTT 5标准已经发布,并且带来了一些令人振奋的新特性,但是在整个应用场景上,业界从版本 35 的过渡可能会持续一段时间。MQTT最大的优点在于可以以极少的代码和有限的带宽,为远程设备提供实时可靠的消息服务。做为一种低开销、低带宽占用的即时通讯协议,MQTT在物联网、小型设备、移动应用等方面有广泛的应用。

2.MQTT协议实现方式

实现MQTT协议需要客户端服务器端通讯完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publish)代理(Broker 服务器)订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。

MQTT传输的消息分为:主题(Topic)负载(payload)两部分:

  • (1)Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload)
  • (2)payload,可以理解为消息的内容,是指订阅者具体要使用的内容
    在这里插入图片描述

3.MQTT 控制报文格式

MQTT协议工作在TCP之上,端和代理之间通过交换预先定义的控制报文来完成通信。MQTT报文有 3 个部分组成,并按下表顺序出现:

Fixed header 固定报头,所有控制报文都包含
Variable header 可变报头,部分控制报文包含
Payload 有效载荷,部分控制报文包含

3.1 固定报头的格式

Bit 7 6 5 4 3 2 1 0
byte 1 MQTT控制报文的类型 用于指定控制报文类型的标志位
byte 2... 剩余长度
  • MQTT控制报文的类型
名字 报文流动方向 描述
Reserved 0 禁止 保留
CONNECT 1 客户端到服务端 客户端请求连接服务端
CONNACK 2 服务端到客户端 连接报文确认
PUBLISH 3 两个方向都允许 发布消息
PUBACK 4 两个方向都允许 QoS 1消息发布收到确认
PUBREC 5 两个方向都允许 发布收到(保证交付第一步)
PUBREL 6 两个方向都允许 发布释放(保证交付第二步)
PUBCOMP 7 两个方向都允许 QoS 2消息发布完成(保证交互第三步)
SUBSCRIBE 8 客户端到服务端 客户端订阅请求
SUBACK 9 服务端到客户端 订阅请求报文确认
UNSUBSCRIBE 10 客户端到服务端 客户端取消订阅请求
UNSUBACK 11 服务端到客户端 取消订阅报文确认
PINGREQ 12 客户端到服务端 心跳请求
PINGRESP 13 服务端到客户端 心跳响应
DISCONNECT 14 客户端到服务端 客户端断开连接
Reserved 15 禁止 保留
  • 剩余长度
    位置: 从第2个字节开始。

    剩余长度(Remaining Length) 表示当前报文剩余部分的字节数,包括可变报头负载的数据。剩余长度不包括用于编码剩余长度字段本身的字节数。

    剩余长度字段使用一个变长度编码方案,对小于128的值它使用单字节编码。更大的值按下面的方式处理。低7位有效位用于编码数据,最高有效位用于指示是否有更多的字节。因此每个字节可以编码128个数值和一个延续位(continuation bit)剩余长度字段最大4个字节

字节数 最小值 最大值
1 0 (0x00) 127 (0x7F)
2 128 (0x80, 0x01) 16 383 (0xFF, 0x7F)
3 16 384 (0x80, 0x80, 0x01) 2 097 151 (0xFF, 0xFF, 0x7F)
4 2 097 152 (0x80, 0x80, 0x80, 0x01) 268 435 455 (0xFF, 0xFF, 0xFF, 0x7F)

分别表示(每个字节的低7位用于编码数据,最高位是标志位):

  • 1个字节时,从0(0x00)到127(0x7f)
  • 2个字节时,从128(0x80,0x01)到16383(0Xff,0x7f)
  • 3个字节时,从16384(0x80,0x80,0x01)到2097151(0xFF,0xFF,0x7F)
  • 4个字节时,从2097152(0x80,0x80,0x80,0x01)到268435455(0xFF,0xFF,0xFF,0x7F)

非负整数X使用 变长编码方案 的算法如下:

   do
  encodedByte = X MOD 128
  X = X DIV 128
 // if there are more data to encode, set the top bit of this byte
 if ( X > 0 )
     encodedByte = encodedByte OR 128
 endif
     'output' encodedByte
while ( X > 0 )

MOD是模运算,DIV是整数除法,OR是位操作或(C语言中分别是%,/,|)

对应的C语言代码如下:

/**
 * Encodes the message length according to the MQTT algorithm
 * @param buf the buffer into which the encoded data is written
 * @param length the length to be encoded
 * @return the number of bytes written to buffer
 */
int MQTTPacket_encode(unsigned char* buf, int length)
{
    
    
	int rc = 0;

	FUNC_ENTRY;
	do
	{
    
    
		char d = length % 128;
		length /= 128;
		/* if there are more digits to encode, set the top bit of this digit */
		if (length > 0)
			d |= 0x80;
		buf[rc++] = d;
	} while (length > 0);
	FUNC_EXIT_RC(rc);
	return rc;
}

剩余长度字段的 变长解码方案 算法如下:

multiplier = 1
value = 0
do
      encodedByte = 'next byte from stream'
      value += (encodedByte AND 127) * multiplier
      if (multiplier > 128*128*128)
        throw Error(Malformed Remaining Length)
      multiplier *= 128
while ((encodedByte AND 128) != 0)

AND是位操作与(C语言中的&)
这个算法终止时,value包含的就是剩余长度的值。

对应的C语言代码如下:

/**
 * Decodes the message length according to the MQTT algorithm
 * @param getcharfn pointer to function to read the next character from the data source
 * @param value the decoded length returned
 * @return the number of bytes read from the socket
 */
int MQTTPacket_decode(int (*getcharfn)(unsigned char*, int), int* value)
{
    
    
	unsigned char c;
	int multiplier = 1;
	int len = 0;
#define MAX_NO_OF_REMAINING_LENGTH_BYTES 4

	FUNC_ENTRY;
	*value = 0;
	do
	{
    
    
		int rc = MQTTPACKET_READ_ERROR;

		if (++len > MAX_NO_OF_REMAINING_LENGTH_BYTES)
		{
    
    
			rc = MQTTPACKET_READ_ERROR;	/* bad data */
			goto exit;
		}
		rc = (*getcharfn)(&c, 1);
		if (rc != 1)
			goto exit;
		*value += (c & 127) * multiplier;
		multiplier *= 128;
	} while ((c & 128) != 0);
exit:
	FUNC_EXIT_RC(len);
	return len;
}

3.2 可变报头

某些MQTT控制报文包含一个可变报头部分。它在固定报头和负载之间。可变报头的内容根据报文类型的不同而不同。可变报头的报文标识符(Packet Identifier)字段存在于在多个类型的报文里。

备注:可变报头不仅指报文标识符,下面只是用它来举例。MQTT 报文类型的不同,就有不同的可变报头。

报文标识符字节,如下:

Bit 7 - 0
byte 1 报文标识符 MSB
byte 2 报文标识符 LSB

很多控制报文的可变报头部分包含一个两字节的报文标识符字段。需要报文标识符的控制报文在下面的表格中列出:

控制报文 报文标识符字段
CONNECT 不需要
CONNACK 不需要
PUBLISH 需要(如果QoS > 0)
PUBACK 需要
PUBREC 需要
PUBREL 需要
PUBCOMP 需要
SUBSCRIBE 需要
SUBACK 需要
UNSUBSCRIBE 需要
UNSUBACK 需要
PINGREQ 不需要
PINGRESP 不需要
DISCONNECT 不需要

客户端服务端彼此独立地分配报文标识符。因此,客户端服务端组合使用相同的报文标识符可以实现并发的消息交换。举例如下:
在这里插入图片描述
在这里插入图片描述

3.3 有效载荷 Payload

某些MQTT控制报文在报文的最后部分包含一个有效载荷,对于PUBLISH来说有效载荷就是应用消息。

有效载荷的控制报文 Control Packets that contain a Payload

控制报文 有效载荷
CONNECT 需要
CONNACK 不需要
PUBLISH 可选
PUBACK 不需要
PUBREC 不需要
PUBREL 不需要
PUBCOMP 不需要
SUBSCRIBE 需要
SUBACK 需要
UNSUBSCRIBE 需要
UNSUBACK 不需要
PINGREQ 不需要
PINGRESP 不需要
DISCONNECT 不需要

4.MQTT 控制报文

具体的每个报文在下面的文章中介绍:《MQTT协议分析总结(二)》

5.参考资料

MQTT 5.0协议中文版:
https://github.com/twoFiveOneTen/MQTT_V5
MQTT Version 5.0:
http://docs.oasis-open.org/mqtt/mqtt/v5.0/cs02/mqtt-v5.0-cs02.htm
MQTT 3.1.1协议中文版:
https://github.com/mcxiaoke/mqtt
MQTT Version 3.1.1:
http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html

猜你喜欢

转载自blog.csdn.net/ZHONGCAI0901/article/details/111600721
今日推荐