赞
踩
MQTT是一个客户端服务端架构的发布/订阅模式的消息传输协议。它的设计思想是轻巧、开放、简单、规范,易于实现。这些特点使得它对很多场景来说都是很好的选择,特别是对于受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT)。
开放消息协议,简单易实现
MQTT协议广泛应用于物联网、移动互联网、智能硬件、车联网、电力能源等领域。
MQTT 使用的底层传输协议(TCP)基础设施。
MQTT 协议通过网络传输应用数据。应用消息通过 MQTT 传输时,它们有关联的服务质量和主题。
使用 MQTT 的程序或设备。客户端总是通过网络连接到服务端。它可以
一个程序或设备,作为发送消息的客户端和请求订阅的客户端之间的中介。服务端
订阅包含一个主题过滤器(Topic Filter)和一个最大的服务质量(QoS)等级。订阅与单个会话(Session)关联。会话可以包含多于一个的订阅。会话的每个订阅都有一个不同的主题过滤器。
附加在应用消息上的一个标签,服务端已知且与订阅匹配。服务端发送应用消息的一个副本给每一个匹配的客户端订阅。
客户端和服务端之间的状态交互。一些会话持续时长与网络连接一样,另一些可以在客户端和服务端的多个连续网络连接间扩展。
MQTT 控制报文的结构
Fixed header 固定报头,所有控制报文都包含 |
---|
Variable header 可变报头,部分控制报文包含 |
Payload 有效载荷,部分控制报文包含 |
固定报头的格式
其中控制报文的类型有
比较重要并且常使用报文类型的有CONNECT,CONNACK,PUBLISH,SUBSCRIBE,SUBACK,PINGREQ,PINGRESP,DISCONNECT。
用于指定控制报文类型的标志位在这里不介绍,在之后介绍每个报文的再说明。
剩余长度(Remaining Length)表示当前报文剩余部分的字节数,包括可变报头和负载的数据。剩余长度不包括用于编码剩余长度字段本身的字节数。也就是剩余长度 = 可变报头 + 有效载荷。
剩余长度的编码方式:
剩余长度字段使用一个变长度编码方案,对小于 128 的值它使用单字节编码。更大的值按下面的方式处理。低 7 位有效位用于编码数据,最高有效位用于指示是否有更多的字节。因此每个字节可以编码 128 个数值和一个延续位(continuation bit)。剩余长度字段最大 4 个字节。
例如,十进制数 64 会被编码为一个字节,数值是 64,十六进制表示为 0x40,。十进制数字
321(=65+2*128)被编码为两个字节,最低有效位在前。第一个字节是 65+128=193。注意最高位为
1 表示后面至少还有一个字节。第二个字节是 2。
剩余长度的范围:
编码伪代码如下:
相应的解码方式如下:
接下里的可变报头和有效载荷将在各个报文里说明。
编码剩余长度
/** \brief 编码剩余长度 * * \param X 剩余长度 * \return 无 * */ void codeRemainLength(unsigned int X) { unsigned encodedByte = 0; //编码 do { encodedByte = X %128; X = X / 128; if(X > 0) { encodedByte = encodedByte | 128; } //输出已编码的剩余长度 printf("%02X ", encodedByte); }while(X > 0); }
解码剩余长度
/** \brief 解码剩余长度 * * \param data 指向已编码的剩余长度数组的首个元素的指针 * \return 无 * */ void decodeRemainLength(const char *data) { unsigned int multiplier = 1; unsigned int value = 0; unsigned char encodedByte = 0; do { encodedByte = *data++; value += (encodedByte & 127) * multiplier; multiplier *= 128; if (multiplier > 128 * 128 * 128) { // throw Error(Malformed Remaining Length) // error 出错 return; } } while ((encodedByte & 128) != 0); //输出已解码的剩余长度 printf("%u", value); }
这里我只挑选一些重要常用的报文并结合实例讲解。
客户端到服务端的网络连接建立后,客户端发送给服务端的第一个报文必须是 CONNECT 报文
第一个字节毫无疑问是0x10;剩余长度 = 可变报头 + 有效载荷,所以剩余长度得之后再计算,并且剩余长度最大占用4个字节,所以我们先留着4个字节的位置出来。
byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
---|---|---|---|---|---|---|---|
10 | ? | ? | ? | ? | ? | ? | ? |
注意一下上面表格的数据都是16进制的数据。
CONNECT 报文的可变报头按下列次序包含四个字段:协议名(Protocol Name),协议级别(Protocol Level),连接标志(Connect Flags)和保持连接(Keep Alive)。
协议名字节构成
协议名的数据都是固定的,直接转换成16进制的数据填入。
byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
---|---|---|---|---|---|---|---|
10 | ? | ? | ? | ? | 00 | 04 | 4D(‘M’) |
51(‘Q’) | 54(‘T’) | 54(‘T’) | ? | ? | ? | ? | ? |
协议级别字节构成
byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
---|---|---|---|---|---|---|---|
10 | ? | ? | ? | ? | 00 | 04 | 4D(‘M’) |
51(‘Q’) | 54(‘T’) | 54(‘T’) | 04 | ? | ? | ? | ? |
连接标志
这里需要根据需要来将相应的位设置为1,具体位的含义请参考MQTT协议,一般用到用户名和密码,所以需要把第6位和第7位设置1,其他位根据需要设置,这里我设置为0xC2。
byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
---|---|---|---|---|---|---|---|
10 | ? | ? | ? | ? | 00 | 04 | 4D(‘M’) |
51(‘Q’) | 54(‘T’) | 54(‘T’) | 04 | C2 | ? | ? | ? |
保持连接时间
这里我设置为300秒,转换为16进制为01 2C,注意高字节在前。
byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
---|---|---|---|---|---|---|---|
10 | ? | ? | ? | ? | 00 | 04 | 4D(‘M’) |
51(‘Q’) | 54(‘T’) | 54(‘T’) | 04 | C2 | 01 | 2C | ? |
CONNECT 报文的有效载荷(payload)包含一个或多个以长度为前缀的字段,可变报头中的标志决定是否包含这些字段。如果包含的话,必须按这个顺序出现:客户端标识符,遗嘱主题,遗嘱消息,用户名,密码。
字段的格式
在上面连接标志中我仅使用到了用户名和密码,所以我们需要给出客户端标识、用户名和密码。
假如客户端标识是"Client1"
则转换为16进制为43 6C 69 65 6E 74 31,数据长度为7,转换为16进制为 00 07(注意,数据长度占用2个字节,高字节在前)。
byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
---|---|---|---|---|---|---|---|
10 | ? | ? | ? | ? | 00 | 04 | 4D(‘M’) |
51(‘Q’) | 54(‘T’) | 54(‘T’) | 04 | C2 | 01 | 2C | 00 |
07 | 43 | 6C | 69 | 65 | 6E | 74 | 31 |
用户名和密码字段同理,就不说了。
最后计算剩余长度,剩余长度 = 可变报头 + 有效载荷,故剩余长度为19个字节,再进行编码最后为0x13。
故最后得到的CONNECT报文为:
byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
---|---|---|---|---|---|---|---|
10 | 13 | 00 | 04 | 4D(‘M’) | 51(‘Q’) | 54(‘T’) | 54(‘T’) |
04 | C2 | 01 | 2C | 00 | 07 | 43 | 6C |
69 | 65 | 6E | 74 | 31 |
CONNACK确认连接请求是服务端发给客户端的报文。
第一个字节是0x20,剩余长度之后再计算。
byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
---|---|---|---|---|---|---|---|
20 | ? | ? | ? | ? | ? | ? | ? |
连接确认标志具体含义可以看MQTT协议。
连接返回码的数值请看下图,可以发现如果服务端发送客户端是0x00,则代表连接已经成功,其他数值则代表出现错误。
我们假如服务端给我们发送过来是连接已被服务端接受,则有
byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
---|---|---|---|---|---|---|---|
20 | ? | ? | ? | ? | 00 | 00 | ? |
该报文没有有效载荷。
该报文很简单,剩余长度等于2,转换为16进制为0x02。
最后得到就是下表。
byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
---|---|---|---|---|---|---|---|
20 | 02 | 00 | 00 |
客户端向服务端发布消息,或者服务端向客户端发布消息。
这里我以客户端向服务端发送消息为例。
第一个字节为0x30,后面的标志位我全设置为0,同样的这些位得根据需求才能确定下来是设置为0还是1。
byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
---|---|---|---|---|---|---|---|
30 | ? | ? | ? | ? | ? | ? | ? |
可变报头按顺序包含主题名和报文标识符。
只有当 QoS 等级是 1 或 2 时,报文标识符(Packet Identifier)字段才能出现在 PUBLISH 报文中。
假设主题为ABC
QoS 等级为0,则主题名长度为3,转换为16进制为 00 03,有
byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
---|---|---|---|---|---|---|---|
30 | ? | ? | ? | ? | 00 | 03 | 41(A) |
42(B) | 43(C) | ? | ? | ? | ? | ? | ? |
有效载荷包含将被发布的应用消息。数据的内容和格式是应用特定的。
假如我们要发送的数据是Hello
,转换之后为48 65 6C 6C 6F ,有
byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
---|---|---|---|---|---|---|---|
30 | ? | ? | ? | ? | 00 | 03 | 41(A) |
42(B) | 43(C) | 48(H) | 65(e) | 6C(l) | 6C(l) | 6F(o) | ? |
剩余长度为10,即0xA0。
byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
---|---|---|---|---|---|---|---|
30 | A0 | 00 | 03 | 41(A) | 42(B) | 43(C) | 48(H) |
65(e) | 6C(l) | 6C(l) | 6F(o) |
客户端向服务端发送 SUBSCRIBE 报文用于创建一个或多个订阅。每个订阅注册客户端关心的一个或多个主题。
注意下,第一个字节是0x82
byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
---|---|---|---|---|---|---|---|
82 | ? | ? | ? | ? | ? | ? | ? |
可变报头包含客户端标识符。
假设我们的客户端标识符是12 34
byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
---|---|---|---|---|---|---|---|
82 | ? | ? | ? | ? | 12 | 34 | ? |
SUBSCRIBE 报文的有效载荷必须包含至少一对主题过滤器 和 QoS 等级字段组合。
假设主题名是abc
则转换后为61 62 63,长度为00 03,QoS 服务等级为00
byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
---|---|---|---|---|---|---|---|
82 | ? | ? | ? | ? | 12 | 34 | 00 |
03 | 61 | 62 | 63 | 00 |
byte1 | byte2 | byte3 | byte4 | byte5 | byte6 | byte7 | byte8 |
---|---|---|---|---|---|---|---|
82 | 08 | 12 | 34 | 00 | 03 | 61 | 62 |
63 | 00 |
其他的报文就不介绍。
源代码下载链接
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。