MQTT协议及安全详解

Posted Doker 多克

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MQTT协议及安全详解相关的知识,希望对你有一定的参考价值。

 1、MQTT概念   

MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(Publish/Subscribe)模式的轻量级通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布,目前最新版本为v3.1.1。MQTT最大的优点在于可以以极少的代码和有限的带宽,为远程设备提供实时可靠的消息服务。做为一种低开销、低带宽占用的即时通讯协议,MQTT在物联网、小型设备、移动应用等方面有广泛的应用。

当然,在物联网开发中,MQTT不是唯一的选择,与MQTT互相竞争的协议有XMPP和CoAP协议等,

MQTT是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT协议是轻量,简单,开放和易于实现的。如:机器与机器(M2M)通信和物联网(loT)。

MQTT 是 IoT 领域的最重要的标准协议之一,广泛用于物联网、车联网、工业物联网、智能家居、智慧城市、电力石油能源等行业。

MQTT 是 AWS IoT Core、 Azure IoT Hub、阿里云物联网平台等顶级云厂商物联网平台标准通信协议,是工业互联网、车联网、智能家居等各个行业以及诸多网关协议上云的首选协议。

EMQX 作为全球最热门、最成熟的 MQTT Broker 之一,提供了「随处运行,无限连接,任意集成」云原生分布式物联网接入平台,一体化的分布式 MQTT 消息服务和强大的 IoT 规则引擎,为高可靠、高性能的物联网实时数据移动、处理和集成提供动力,助力企业快速构建关键业务的 IoT 平台与应用。
 

2、设备网络构成

设备主要有三种角色:代理(broker)、发布者(publisher)、订阅者(subscriber)。

  • 代理:服务端,是传输的桥梁,一个网络中通常只有一个。负责处理客户端的连接、订阅、发布。
  • 发布者:客户端,负责发布消息到代理,代理将数据转发给订阅者。
  • 订阅者:客户端,负责接收代理的转发消息

3、MQTT协议原理

3.1 MQTT协议实现方式

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

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

  1)Topic,可以理解为消息类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload);

  2)payload,可以理解为消息的内容,是指订阅者具体要使用的内容

3.2网络传输与应用消息

MQTT会构建底层网络传输:它将建立客户端到服务器的连接,提供两者之间的一个有序的,无损的,基于字节流的双向传输

当应用数据通过MQTT网络发送时,MQTT会把与之相关的服务质量(Qos)和主题名(Topic)相关连

3.3 MQTT客户端

一个使用MQTT协议的应用程序或者设备,它总是建立到服务器的网络连接。客户端可以:

  1)发布其他客户端可能会订阅的信息

  2)订阅其他客户端发布的消息

  3)退订或删除应用程序的消息

  4)断开与服务器连接

3.4 MQTT服务器

MQTT服务器以称为“消息代理”(Broker),可以是一个应用程序或一台设备,它是位于消息发布者和订阅者之间,它可以:

  1)接受来自客户的网络连接;

  2)接受客户发布的应用消息;

  3)处理来自客户端的订阅和退订请求

  4)向订阅的客户转发应用程序消息

3.5 MQTT协议中的订阅,主题,会话

(1)订阅(Subscription)

订阅包含主题筛选器(Topic Filter)和最大服务质量(Qos)。订阅会与一个会话(session)关联。一个会话可以包含多个订阅。每一个会话中的每个订阅都有一个不同的主题筛选器

(2)会话(session)

每个客户端与服务器建立连接后就是一个会话,客户端和服务器之间有状态交互。会话存在一个网络之间,也可能在客户端和服务器之间跨越多个连接的网络连接

(3)主题(Topic Name)

连接到一个应用程序消息的标签,该标签与服务器的订阅匹配。服务器会将消息发送给订阅所匹配标签的每个客户端

(4)主题筛选器(Topic Filter)

一个对主题名通配符筛选器,在订阅表达式中使用,表示订阅所匹配到的多个主题

(5)负载(Payload)

消息订阅者所具体接收的内容

3.6 MQTT协议中的方法

MQTT协议中定义了一些方法(也被称为动作),来表示对确定资源所进行操作。这个资源可以代表预先存在的数据或动态生成数据,这取决于服务器的实现。通常来说,资源指服务器上的文件或输出。主要方法有:

  1)Connect,等待与服务器建立连接

  2)Disconnect,等待MQTT客户端完成所做的工作,并与服务器断开TCP/IP会话

  3)Subscribe,等待完成订阅

  4)UnSubscribe,等待服务器取消客户端的一个或多个topics订阅

  5)Publish,MQTT客户端发送消息请求,发送完成后返回应用程序线程

3.7 连接保活心跳机制(Keep Alive Timer)

MQTT客户端可以设置一个心跳间隔时间(Keep Alive Timer),表示在每个心跳间隔时间内发送一条消息。如果在这个时间周期内,没有业务数据相关的消息,客户端会发一个PINGREQ消息,相应的,服务器会返回一个PINGRESP消息进行确认。如果服务器在一个半(1.5)心跳间隔时间周期内没有收到来自客户端的消息,就会断开与客户端的连接。心跳间隔时间最大值大约可以设置为18个小时,0值意味着客户端不断开。

3.8 MQTT的安全

由于MQTT运行于TCP层之上并以明文方式传输,这就相当于HTTP的明文传输,使用Wireshark可以完全看到MQTT发送的所有消息,消息指令一览无遗。

这样可能会产生以下风险:
设备可能会被盗用;
客户端和服务端的静态数据可能是可访问的(可能会被修改);
协议行为可能有副作用(如计时器攻击);
拒绝服务攻击;
通信可能会被拦截、修改、重定向或者泄露;
虚假控制报文注入。

作为传输协议,MQTT仅关注消息传输,提供合适的安全功能是开发者的责任。安全功能可以从三个层次来考虑——应用层、传输层、网络层。
应用层:在应用层上,MQTT提供了客户标识(Client Identifier)以及用户名和密码,可以在应用层验证设备。
传输层:类似于HTTPS,MQTT基于TCP连接,也可以加上一层TLS,传输层使用TLS加密是确保安全的一个好手段,可以防止中间人攻击。客户端证书不但可以作为设备的身份凭证,还可以用来验证设备。
网络层:如果有条件的话,可以通过拉专线或者使用VPN来连接设备与MQTT代理,以提高网络传输的安全性。

认证
MQTT支持两种层次的认证:
应用层:MQTT支持客户标识、用户名和密码认证;
传输层:传输层可以使用TLS,除了加密通讯,还可以使用X509证书来认证设备。

客户标识
MQTT客户端可以发送最多65535个字符作为客户标识(Client Identifier),一般来说可以使用嵌入式芯片的MAC地址或者芯片序列号。虽然使用客户标识来认证可能不可靠,但是在某些封闭环境或许已经足够了。

用户名和密码
MQTT协议支持通过CONNECT消息的username和password字段发送用户名和密码。
用户名及密码的认证使用起来非常方便,不过由于它们是以明文形式传输,所以使用抓包工具就可以轻易的获取。
一般来说,使用客户标识、用户名和密码已经足够了,比如支持MQTT协议连接的OneNET云平台,就是使用了这三个字段作为认证。如果感觉还不够安全,还可以在传输层进行认证。

在传输层认证
在传输层认证是这样的:MQTT代理在TLS握手成功之后可以继续发送客户端的X509证书来认证设备,如果设备不合法便可以中断连接。使用X509认证的好处是,在传输层就可以验证设备的合法性,在发送CONNECT消息之前便可以阻隔非法设备的连接,以节省后续不必要的资源浪费。而且,MQTT协议运行在使用TLS时,除了提供身份认证,还可以确保消息的完整性和保密性。
 

3.9 选择用户数据格式

MQTT协议只实现了传送消息的格式,并没有限制用户协议需要按照一定的风格,因此在MQTT协议之上,我们需要定义一套自己的通信协议。比如说,发布者向设备发布一条打开消息,设备可以回复一个消息并携带返回码,这样的消息格式是使用二进制、字符串还是JSON格式呢?下面就简单做个选型参考。

十六进制/二进制
MQTT原本就是基于二进制实现的,所以用户协议使用二进制实现是一个不错的选择。虽然失去了直观的可读性,但可以将流量控制在非常小。其实对于单片机开发者来说十六进制并不陌生,因为单片机寄存器都是以位来操作的,芯片间通信也会使用十六进制/二进制。而对于没有单片机开发经验的工程师来说,十六进制/二进制可能就太原始了。下面我们继续看看还有没有其他方案。

字符串
对单片机开发者来说,字符串也是一个选择。比如通过串口传输的AT指令就是基于字符串通信的。使用字符串方便了人阅读,但是对高级语言开发者来说,字符串依旧不是最佳选择,恐怕键值对(Key-Value)才是最优形式。

JSON
JSON中文全称是javascript对象标记语言,在这门语言中,一切都是对象。因此,任何支持的类型都可以通过JSON来表示,例如字符串、数字、对象、数组等。 

XML
MQTT协议只负责通信部分,用户协议可以自己选择,当然也可以选择复杂又冗长的XML格式
 

详细内容参考:MQTT Version 5.0

网络编程——物联网利器MQTT通信协议详解

引言

书接上文网络编程——物联网利器MQTT通信协议详解(二) 系列文章链接:

五、MQTT协议控制包结构概述

一个字节有8个位,从0到7。位7是最高有效位,位0是最低有效位。MQTT协议中整型数据值是16位大端序列:高阶字节在低阶字节之前。即一个16位的字被放到网络上的时候,前面是最高有效位,后面是最低有效位。

在MQTT协议中,一个MQTT控制包由:**固定头(Fixed header)、可变头(Variable header)、载荷(Payload)**三部分构成。

  • 固定头(Fixed header)——存在于所有MQTT控制包中,表示控制包类型及控制包的分组类标识。
  • 可变头(Variable header)——存在于部分MQTT控制包中,控制包类型决定了可变头是否存在及其具体内容。
  • 载荷(Payload)——存在于部分MQTT控制包中,表示客户端收到的具体内容。

1、 MQTT固定包头

每一个MQTT控制包都包含一个固定包头(2个字节),其结构如下:

|Bit         |7       |6       |5       |4       |3       |2       |1           |0
|byte 1      |MQTT Control Packet type           |Flags specific to each MQTT Control Packet type
|byte 2      |Remaining Length

MQTT控制包的固定头大部分都是2字节依次由包类型标识和剩余长度组成。

1.1、 MQTT控制包类型

固定头中的字节1中 [7-4]位相于一个4位的无符号值用于表示MQTT控制包类型,取值及描述如下:

NameValueDirection of flowDescription
Reserved0ForbiddenReserved
CONNECT1Client to Server
CONNACK2Server to ClientConnect acknowledgment
PUBLISH3Client to Server or Server to ClientPublish message
PUBACK4Client to Server or Server to ClientPublish acknowledgment
PUBREC5Client to Server or Server to ClientPublish received (assured delivery part 1)
PUBREL6Client to Server or Server to ClientPublish release (assured delivery part 2)
PUBCOMP7Client to Server or Server to ClientPublish complete (assured delivery part 3)
SUBSCRIBE8Client to ServerClient subscribe request
SUBACK9Server to ClientSubscribe acknowledgment
UNSUBSCRIBE10Client to ServerUnsubscribe request
UNSUBACK11Server to ClientUnsubscribe acknowledgment
PINGREQ12Client to ServerPING request
PINGRESP13Server to ClientPING response
DISCONNECT14Client to ServerClient is disconnecting
Reserved15ForbiddenReservedControl Package

1.2、 标识位

固定包头字节1中剩下的位[3-0]包含了每个MQTT控制包类型的特殊标识,如下表 - Flag Bits。

Control PackageFixed header flagsbit3bit2bit1bit0
CONNECTReserved0000
CONNACKReserved0000
PUBLISHUsed in MQTT 3.1.1DUP1QoS2QoS2RETAIN3
PUBACKReserved0000
PUBRECReserved0000
PUBRELReserved0010
PUBCOMPReserved0000
SUBSCRIBEReserved0010
SUBACKReserved0000
UNSUBSCRIBEReserved0010
UNSUBACKReserved0000
PINGREQReserved0000
PINGRESPReserved0000
DISCONNECTReserved0000

表中被标识为“预留”的标识位也必须赋值,如果收到不可用的标识,接收方必须关闭网络连接。DUP2 = 重复发送PUBLISH控制包 QoS2 = PUBLISH质量服务 RETAIN3 = PUBLISH保留标识 关于PUBLISH控制包的DUP,QoS,以及RETAIN标识

  • DUP——发布消息的副本。用来在保证消息的可靠传输,如果设置为1,则在下面的变长中增加MessageId,并且需要回复确认,以保证消息传输完成,但不能用于检测消息重复发送。

  • QoS——发布消息的服务质量,即:保证消息传递的次数

1.3、 剩余长度

从第二个字节开始,剩余长度是指当前包中的剩余字节,包括可变包头的数据以及载荷。剩余长度不包含用来编码剩余长度的字节。剩余长度使用了一种可变长度的结构来编码,这种结构使用单一字节表示0-127的值,当大于127的值如下处理:每个字节的低7位用来编码数据,最高位用来表示是否还有后续字节。因此每个字节可以编码128个值,再加上一个标识位,剩余长度最多可以用四个字节来表示

这将允许应用发送最多256M大小的控制包。这个数字用16进制表示为:0xFF,0xFF,0xFF,0x7F。Table 2.4 展示了随着字节数的增多,剩余长度可表示的值。

2、 MQTT可变头

某些类型的**MQTT控制包包含一个位于固定包头和载荷之间的可变包头结构,**其内容取决于包的类型。

2.1、包唯一标识

很多类型控制包(像PUBLISH (QoS > 0)、PUBACK、PUBREC、PUBREL、PUBCOMP、SUBSCRIBE、SUBACK、UNSUBSCRIBE、UNSUBACK等类型控制包)中都包括一个2字节的控制包标识字段且必须包含非零的唯一标识

Control PacketPacket Identifier field
CONNECTNO
CONNACKNO
PUBLISHYES (If QoS > 0)
PUBACKYES
PUBRECYES
PUBRELYES
PUBCOMPYES
SUBSCRIBEYES
SUBACKYES
UNSUBSCRIBEYES
UNSUBACKYES
PINGREQNO
PINGRESPNO
DISCONNECTNO

YES 代表发送该种控制包时必须包含包唯一标识。

每次客户端发送上述控制包的时候,必须分配一个未使用过的唯一标识。如果一个客户端重新发送一个特别的控制包,必须使用相同的唯一标识符,唯一标识会在客户端收到相应的确认包之后变为可用。例如PUBLIST在QoS1的时候对应PUBACK;而在QoS2时对应PUBCOMP;对于SUBSCRIBE和UNSUBSCRIBE对应SUBACK和UNSUBACK;服务端发送QoS>0的PUBLISH时,上述内容同样适用。但QoS为0的PUBLISH包不允许包含唯一标识。PUBACK,PUBREC,PUBREL包的唯一标识必须和对应的PUBLISH包相同;同样的SUBACK和UNSUBACK的唯一标识必须与对应的SUBSCRIBE和UNSUBSCRIBE包相同。由于客户端和服务端各自独立分配唯一标识。因此,**一对客户端和服务端交换数据的时候可以使用相同的唯一标识。**例如这种情况客户端发送一个PUBLISH包,唯一标识位0x1234,在收到相应的PUBACK之前,接着先收到了一个服务端发来的不同的PUBLISH包,唯一标识同样是0x1234。

Client                     Server
PUBLISH Packet Identifier = 0x1234 ->
<- PUBLISH Packet Identifier = 0x1234
PUBACK Packet Identifier = 0x1234 ->
<- PUBACK Packet Identifier = 0x1234

3、载荷 Payload

一些MQTT控制包的最后一部分包含载荷,PUBLISH包相当于应用消息。Table 2.6 - Control Packets that contain a Payload显示了控制包对载荷的需求。

Control PacketPayload
CONNECTRequired
CONNACKNone
PUBLISHOptional
PUBACKNone
PUBRECNone
PUBRELNone
PUBCOMPNone
SUBSCRIBERequired
SUBACKRequired
UNSUBSCRIBERequired
UNSUBACKNone
PINGREQNone
PINGRESPNone
DISCONNECTNone

Required 表示必须包含载荷部分,None 表示没有载荷。

4、MQTT控制包之CONNECT 包的结构

CONNECT——客户端请求连接服务器

客户端请求连接服务器,客户端和服务端建立网络连接后,第一个从客户端发送给服务端的包必须是CONNECT包。每个网络连接客户端只能发送一次CONNECT包,服务端必须把客户端发来的第二个CONNECT包当作违反协议处理,并断开与客户端的连接。载荷包含一个或多个编码字段,用来指定客户端的唯一标识,话题,信息,用户名和密码。除了客户端唯一标识,其他都是可选项,是否存在取决于可变包头里的标识。

4.1、固定包头

CONNECT包的固定包头结构和上面MQTT固定头所讲的结构一致。

Figure 3.1 - CONNECT Packet fixed header

|Bit     |7       |6       |5       |4       |3       |2       |1       |0
|byte 1  |MQTT Control Packet type (1)       |Reserved
|        |0       |0       |0       |1       |0       |0       |0       |0
|byte 2… |Remaining Length

CONNECT包的剩余长度可变包头长度(10字节)加上载荷的长度

4.2、可变包头

CONNECT包的可变包头由四个字段按照如下顺序构成:协议名字协议等级连接标识保持连接

4.2.1、协议名

协议名字是一个UTF-8编码的全大写的字符串“MQTT”,这个字符串的偏移量和长度不会在未来版本的MQTT规范中有所改变。如果协议名字不正确,服务端可以选择断开连接,也可以选择基于其他规范继续保持连接。防火墙机制可以用协议名来鉴别MQTT传输

4.2.2、协议等级

用8位无符号值表示客户端的版本等级,例如3.1.1版本的协议等级是4(0x04)。如果协议等级不被服务端支持,服务端必须响应一个包含代码0x01(不接受的协议等级)CONNACK包,然后断开和客户端的连接。

4.2.3、连接标识

1字节连接标识包含了一些参数来指定MQTT的连接行为,它也能表示负载中的字段是否存在。服务端必须验证CONNECT控制包的预留字段是否为0,如果不为0断开与客户端的连接。

Bit76543210
User Name FlagPassword FlagWill RetainWill QoSWill QoSWill FlagClean SessionReserved
byte 8XXXXXXX0

服务端必须验证CONNECT控制包的预留字段是否为0,如果不为0断开与客户端的连接。

4.2.3.1、Clean Session

连接标识字节的bit 1指明了会话状态的处理方式。客户端和服务端可以存储会话状态,以便能够在一系列的网络连接中可靠的传递消息,这个bit 1用来控制会话状态的生命周期。

  • 当CleanSession被设置为0,服务器必须根据当前的会话状态恢复与客户端的通信(客户端的唯一标识作为会话的标识)。如果没有与客户端唯一标识相关的会话,服务端必须创建一个新的会话,客户端和服务端在断开连接后必须存储会话。当CleanSession为0的会话断开后,服务器还必须将所有和客户端订阅相关的QoS1和QoS2的消息作为会话状态的一部分存储起来,也可以选择把QoS0的消息也存储起来。

  • 当CleanSession被设置为1,客户端和服务端必须断开之前的会话启动一个新的会话。只要网络连接存在会话就存在,一个会话的状态数据一定不能被随后的会话复用,客户端和服务端不需要自动处理状态的删除。

客户端会话状态:

  • 已经发送到服务端,但没有收到确认的QoS 1和QoS 2消息
  • 接收到的从服务端QoS 2消息,还没有收到确认的

服务端会话状态:

  • 即使会话状态为空,会话本身也必须存在。
  • 客户端的订阅。
  • 发送到客户端的但没有得到确认的QoS 1和QoS 2消息。
  • 等待发送到客户端的QoS 1和QoS 2消息。
  • 从客户端收到的QoS 2消息,但还没有确认的。
  • 可选项,等待发送到客户端的QoS 0消息。

在服务端,保留消息不属于会话状态,它们不必在会话结束的时候被删除。

4.2.3.2、Will Flag

连接标识的bit 2,如果Will Flag被设置为1,如果连接请求被接受,服务端必须存储一个Will Message并和网络连接关联起来。之后在网络连接断开的时候必须发布Will Message,除非服务端收到DISCONNECT包删掉了Will Message。另外Will Message会在某些情况下发布包括(但不限于):

  • 服务端发现I/O错误或网络失败。
  • 客户端在Keep Alive时间内通信失败。
  • 客户端没有发送DISCONNECT包就关闭了网络连接。
  • 服务端因协议错误关闭了网络连接。

如果Will Flag被设置为1,连接标识中的Will QoS和Will Retain字段将会被服务端用到而且Will Topic和Will Message字段必定会出现在载荷中。一旦被发布过或者服务端收到了客户端的DISCONNECT包,Will Message必须从服务端存储的会话状态中移除;如果Will Flag被设置为0,连接标识中的Will QoS和Will Retain字段必须设置为零且Will Topic和Will Message字段不能够出现在载荷中,Will Message将不会再网络连接结束的时候发布。例如服务端关闭或故障,只能推迟Will Message的发布,直到服务端重启。如果这种情况发生,就会出现从服务器故障到Will Message被发布之间的延迟。

4.2.3.3、Will QoS

连接标识的bit 4和3,这两个bit表示发布Will Message时使用QoS的等级。如果Will Flag设置为0,那么Will QoS也必须设置为0;如果Will Flag设置为1,那么Will QoS的值可是是0(0x00),1(0x01),2(0x02)。一定不会是3(0x03)。

4.2.3.4、Will Retain

连接标识的bit 5,表示Will Message在发布之后是否需要保留。如果Will Flag设置为0,那么Will Retain必须是0;如果Will Flag设置为1:

  • 如果Will Retain设置为0,那么服务端必须发布Will Message,不必保存[MQTT-3.1.2-16]。
  • 如果Will Retain设置为1,那么服务端必须发布Will Message,并保存[MQTT-3.1.2-17]。
4.2.3.5、Password Flag

连接标识的bit 6。

  • 如果Password Flag设置为0,那么密码不必出现在载荷中

  • 如果Password Flag设置为1,那么密码必须出现在载荷中

  • 如果User Name Flag设置为0,那么Password Flag必须设置为0

4.2.3.6、User Name Flag

连接标识的bit 7。

  • 如果User Name Flag设置为0,那么用户名不必出现在载荷中

  • 如果User Name Flag设置为1,那么用户名必须出现在载荷中

4.2.4、Keep Alive
Figure 3.5 Keep Alive bytes

|Bit        |7 |6 |5 |4 |3 |2 |1 |0
|byte 9     |Keep Alive MSB
|byte 10    |Keep Alive LSB

紧跟连接标识后用16-bit字表示的Keep Alive是以秒为单位的时间间隔指的是客户端从发送完成一个控制包到开始发送下一个的最大时间间隔。客户端有责任确保两个控制包发送的间隔不能超过Keep Alive的值。如果没有其他控制包可发,客户端必须发送PINGREQ包。客户端可以在任何时间发送PINGREQ包,不用关心Keep Alive的值,用PINGRESP来判断与服务端的网络连接是否正常。如果Keep Alive的值非0,而且服务端在一个半Keep Alive的周期内没有收到客户端的控制包,服务端必须作为网络故障断开网络连接。而如果客户端在发送了PINGREQ后,在一个合理的时间都没有收到PINGRESP包,客户端应该关闭和服务端的网络连接。Keep Alive的值为0,就关闭了维持的机制。在这种情况下,服务端不会断开静默的客户端。

服务端在任何时候都可以断开它认为不活跃或没有响应的客户端,而不需要遵照客户端提供的Keep Alive。

4.3、载荷

CONNECT包的载荷可以包含一个或多个带有长度前缀的字段,这些字段(客户端的ClientID、订阅的Topic、Message以及用户名和密码等)是否存在取决于可变包头的标识。如果存在,必须按照这样的顺序:客户端唯一标识,Will Topic,Will Message,User Name,Password

4.3.1、客户端标识

客户端标识(ClientId)必须存在且是以UTF-8编码的字符串存储于CONNECT包载荷的第一个字段,服务端必须能够接纳的ClientId是长度为1到23的UTF-8编码的字节,而且只包含字符“0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”当然服务端也可以接纳大于23个字节编码的ClientId,服务端也可以允许ClientId包含上述字符之外的字符。服务端允许客户端提供0字节长度的ClientId时,服务端必须把这也当作一个特例,作为客户端的唯一ClientId正常处理。如果客户端提供了一个0字节的ClientId,客户端必须设置CleanSession为1,如果客户端提供了一个0字节的ClientId且CleanSession设置为0,那么服务端必须响应一个CONNACK包,带有代码0x02(标识被拒绝),然后关闭网络连接,如果服务端拒绝了ClientId,必须返回一个CONNACK包,带有代码0x02(标识被拒绝),然后关闭网络连接。

4.3.2、Will Topic

如果Will Flag设置为1,Will Topic是载荷的下一个字段且必须是UTF-8编码的字符串。

4.3.3 Will Message

如果Will Flag设置为1,Will Message是载荷的下一个字段,Will Message定义了将要发布给Will Topic的应用消息。这个字段的前两个字节表示长度,后面是一段字节序列。长度代表字节序列的字节数,不包括长度本身的两个字节。

当Will Message被发布给Will Topic的时候,只是包括字节序列那部分数据,不包含代表长度的头两个字节。

4.3.4 User Name

如果User Name Flag设置为1,这将是载荷的下一个字段。User Name必须是1.5.3节定义的UTF-8编码的字符串[MQTT-3.1.3-11]。服务端根据它来认证和授权。

4.3.5 Password

如果Password Flag设置为1,这将是载荷的下一个字段。Password字段包含0-65535字节的二进制数据,数据前面有两个字节来定义二进制数据占用的字节数(不包括这两个字节本身)。

5、MQTT 控制包之CONNACK 包的结构

服务端可能在同一个TCP端口或其他网络终端会支持多种协议(包括本协议的早期版本)。如果服务端确认协议是MQTT 3.1.1,那么它必须确认下列连接请求:

  • 如果服务端没有收到CONNECT包,在网络连接建立后的一个合理的时间内,服务端应该关闭网络连接。

  • 服务端必须验证CONNECT包是否符合规范,如果不符合,关闭网络连接且不需要发送CONNACK

  • 服务端可以对CONNECT包的内容进行进一步的限制验证,可以检查认证和授权。如果任何检查失败,应该发送适合的CONNACK包,然后关闭网络连接。

如果验证成功,服务端执行下列步骤。

  • 如果ClientId标识的客户端已经连接了服务端,服务端必须断开已经存在的客户端

  • 服务端必须处理相应的CleanSession

  • 服务端必须用一个包含返回码0的CONNACK包作为CONNECT包的确认。

  • 启动消息传递和维持监测,客户端可以在发送CONNECT包之后立马发送其他控制包,客户端不需要等待CONNACK包。如果服务端拒绝了CONNECT,就不要处理客服端在CONNECT包之后发送的任何数据

CONNACK包是服务端发送的用来相应客户端CONNECT包的一种数据包且是从服务端发往客户端的第一个包。如果客户端在一个合理的时间内没有收到服务端的CONNACK包,客户端应该关闭网络连接,“合理”的时间取决于应用的类型和沟通。

5.1、固定包头

除了**剩余长度是可变包头的长度(即2个字节)**外,其固定包头整体结构与其他包一致。

5.2、可变包头

Figure 3.9 - CONNACK Packet variable header

|        |Description        |7 |6 |5 |4 |3 |2 |1 |0
|Connect Acknowledge Flags   |Reserved            |SP1
|byte 1  |                   |0 |0 |0 |0 |0 |0 |0 |X
|Connect Return code
|byte 2  |                   |X |X |X |X |X |X |X |X

可变包头 依次由连接确认标识(中存在Session Present)、连接返回码组成,无载荷部分

5.2.1、连接确认标识

可变包头的字节1是“连接确认标识”,其中位7-1是保留位必须设置为0

5.2.1、Session Present

Session Present标识使得客户端能够建立连接,不论客户端和服务端在是否已经存储了会话状态上达成共识,连接确认标识”的位0(SP1)是Session Present标识:

  • 如果服务端接受了一个CleanSession设置为1的连接,服务端必须将CONNACK包中的Session Present设置为0,并且CONNACK包的返回码也设置为0

  • 如果服务端接受了一个CleanSession设置为0的连接,Session Present的值取决于服务端是否已经存储了客户端Id对应的绘画状态。如果服务端已经存储了会话状态,CONNACK包中的Session Present必须设置为1[MQTT-3.2.2-2]。如果服务端没有存储会话状态,CONNACK包的Session Present必须设置为0。另外CONNACK包中的返回码必须设为0

一旦会话的初始设置完成,存储会话状态的客户端会期望服务端也存储了会话状态。万一Session Present的值不符合预期,客户端可以选择是继续处理这个会话还是断开连接。客户端可以通过断开连接,把CleanSession设置为1重新连接,然后再断开连接,来决定客户端和服务端的会话状态。

5.2.3、连接返回码

可变包头的第二个字节是连接返回码,无符号单字节连接返回码字段的值如下表所列

ValueReturn Code ResponseDescription
00x00 Connection AcceptedConnection accepted
10x01 Connection Refused, unacceptable protocol versionThe Server does not support the level of the MQTT protocol requested by the Client
20x02 Connection Refused, identifier rejectedThe Client identifier is correct UTF-8 but not allowed by the Server
30x03 Connection Refused, Server unavailableThe Network Connection has been made but the MQTT service is unavailable
40x04 Connection Refused, bad user name or passwordThe data in the user name or password is malformed
50x05 Connection Refused, not authorizedThe Client is not authorized to connect
6-255Reserved for future use

如果服务端收到了一个格式良好的CONNECT包,但是服务端由于某种原因不能处理,那么服务端应该尝试发送一个带有非零返回码的CONNACK包。如果服务端发送了一个包含非零返回码的CONNACK包,必须关闭网络连接

如果上表中的返回码都不适用,那么服务端必须直接关闭网络连接,不发送CONNACK包

未完待续…

以上是关于MQTT协议及安全详解的主要内容,如果未能解决你的问题,请参考以下文章

网络编程——物联网利器MQTT通信协议详解

网络编程——物联网利器MQTT通信协议详解

MQTT和CoAP哪个最可能成为未来物联网通信标准协议?

网络编程——物联网利器MQTT通信协议详解

网络编程——物联网利器MQTT通信协议详解

物联网数据传输协议MQTT介绍与应用开发详解