Appearance
网络基础知识
在局域网里面可以很简单的控制设备, 但是不是在局域网的时候就需要引入服务器
在一个设备上面使用ip表示一个设备, 一个设备上不同的程序使用的不同的端口, 一般来说80端口是http端口, 22端口是ssh服务端口
IP和端口都很珍贵, 不是所有的设备都有公网的ip, 局域网的ip是192.168.x.x, 一般一个路由器有一个公网ip,
一般一个设备在访问网络的时候, 把需求发送给路由器, 路由器记录以后, 路由器进行转发, 构造出一个新的数据包, 源被修改, 但是目的不变, 通过互联网进行发送, 在服务器接受以后返回数据的时候会给路由器, 路由器会再转发给设备
路由器在接受到来路不明的信息会进行屏蔽, 起到保护作用
所以在使用开发板进行上网的时候如果没有一个服务器进行中介, 接收到的路由器会对数据进行丢弃
解决方案
两个服务器都连接到服务器, 在发送数据以后由服务器进行传输数据, 服务器上面需要运行一些软件, 进行数据的传输, 一般使用的是MQTT(一种优化过的协议, 适合传输比较短的数据)
一般使用的软件是
MQTT
mqttclient/README_CN.md at master · jiejieTop/mqttclient (github.com)
kawaii-mqtt源码:
博客
APP
可以理解为一个中央电视台, 接受发布者的网络连接, 以及用户的网络连接, 记者会发布消息, 之后转发给订阅他的用户
记者和观众都是客户端
一般是服务器应用先运行, 之后建立连接, 选择订阅的栏目, 用户以及记者需要时不时ping一下中央电视台确保连接, 中央电视台发送ping说明自己还存在
开发板: 连接服务器, 订阅消息, 读MQTT包(有可能一直读不到, 需要有一个超时时间, 没有数据就返回, 在某种情况下发出ping包确认对方的存在), 处理收到的数据
MQTT 数据包结构
固定头(Fixed header)
,存在于所有MQTT
数据包中,表示数据包类型及数据包的分组类标识;可变头(Variable header)
,存在于部分MQTT
数据包中,数据包类型决定了可变头是否存在及其具体内容;消息体(Payload)
,存在于部分MQTT
数据包中,表示客户端收到的具体内容;
整体MQTT的消息格式如下图所示;
2.1 MQTT
固定头
固定头
存在于所有MQTT
数据包中,其结构如下:
下面简单分析一下固定头的消息格式;
MQTT
消息类型 / message type
**位置:**byte 1, bits 7-4。
4位的无符号值,类型如下:
名称 | 值 | 流方向 | 描述 |
---|
标识位 / DUP
这里的reserved表示的是后面的数据暂时没有含义, 需要按照给出的数据进行设置
**位置:**byte 1, bits 3-0。
在不使用标识位的消息类型中,标识位被作为保留位。如果收到无效的标志时,接收端必须关闭网络连接:
数据包 | 标识位 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|
DUP
:发布消息的副本。用来在保证消息的可靠传输,如果设置为 1,则在下面的变长中增加MessageId,并且需要回复确认,以保证消息传输完成,但不能用于检测消息重复发送。QoS
发布消息的服务质量(前面已经做过介绍),即:保证消息传递的次数00
:最多一次,即:<=101
:至少一次,即:>=110
:一次,即:=111
:预留
RETAIN
:发布保留标识,表示服务器要保留这次推送的信息,如果有新的订阅者出现,就把这消息推送给它,如果设有那么推送至当前订阅者后释放。
剩余长度(Remaining Length)
位置:byte 1。
固定头的第二字节用来保存变长头部和消息体的总大小的,但不是直接保存的。这一字节是可以扩展,其保存机制,前7位用于保存长度,后一部用做标识。当最后一位为 1时,表示长度不足,需要使用二个字节继续保存。这一个的长度最大4字节
高位在后, 标志位不计算大小
2.2 MQTT
可变头 / Variable header
MQTT
数据包中包含一个可变头,它驻位于固定的头和负载之间。可变头的内容因数据包类型而不同,较常的应用是做为包的标识:
Bit | 7 — 0 |
---|
很多类型数据包中都包括一个2字节的数据包标识字段,这些类型的包有:
PUBLISH (QoS > 0)、PUBACK、PUBREC、PUBREL、PUBCOMP、SUBSCRIBE、SUBACK、UNSUBSCRIBE、UNSUBACK
SUBSCRIBE,UNSUBSCRIBE和PUBLISH(QoS大于0)控制报文必须包含一个非零的16位报文标识符(Packet Identifier)[MQTT-2.3.1-1]。客户端每次发送一个新的这些类型的报文时都必须分配一个当前未使用的报文标识符 [MQTT-2.3.1-2]。如果一个客户端要重发这个特殊的控制报文,在随后重发那个报文时,它必须使用相同的标识符。当客户端处理完这个报文对应的确认后,这个报文标识符就释放可重用。QoS 1的PUBLISH对应的是PUBACK,QoS 2的PUBLISH对应的是PUBCOMP,与SUBSCRIBE或UNSUBSCRIBE对应的分别是SUBACK或UNSUBACK [MQTT-2.3.1-3]。发送一个QoS 0的PUBLISH报文时,相同的条件也适用于服务端 [MQTT-2.3.1-4]。
QoS等于0的PUBLISH报文不能包含报文标识符 [MQTT-2.3.1-5]。
PUBACK, PUBREC, PUBREL报文必须包含与最初发送的PUBLISH报文相同的报文标识符 [MQTT-2.3.1-6]。类似地,SUBACK和UNSUBACK必须包含在对应的SUBSCRIBE和UNSUBSCRIBE报文中使用的报文标识符 [MQTT-2.3.1-7]。
2.3 Payload
消息体
Payload
消息体是MQTT
数据包的第三部分,CONNECT、SUBSCRIBE、SUBACK、UNSUBSCRIBE四种类型的消息 有消息体:
CONNECT
,消息体内容主要是:客户端的ClientID、订阅的Topic、Message以及用户名和密码SUBSCRIBE
,消息体内容是一系列的要订阅的主题以及QoS
。SUBACK
,消息体内容是服务器对于SUBSCRIBE
所申请的主题及QoS
进行确认和回复。UNSUBSCRIBE
,消息体内容是要订阅的主题。
实际发送的数据要求是UTF-8格式的, 如果赎金不是这一个格式的, 可能会被丢弃
传输的数据(有效荷载)
在MQTT中,“有效荷载”(Payload)指的是实际的数据部分,即发送的消息内容,不包括MQTT协议头部信息。
通过案例理解 MQTT 主题与通配符 | EMQ (emqx.com)
主题
MQTT 协议根据主题来转发消息。主题通过 /
来区分层级,类似于 URL 路径,例如:
chat/room/1
sensor/10/temperature
sensor/+/temperature
通过案例理解 MQTT 主题与通配符 | EMQ (emqx.com)
不同于消息队列中的主题(比如 Kafka 和 Pulsar),MQTT 主题不需要提前创建。MQTT 客户端在订阅或发布时即自动的创建了主题,开发者无需再关心主题的创建,并且也不需要手动删除主题。
MQTT 主题通配符包含单层通配符
+
及多层通配符#
,主要用于客户端一次订阅多个主题。只能用于订阅
加号 (“+” U+002B) 是用于单个主题层级匹配的通配符。在使用单层通配符时,单层通配符必须占据整个层级,例如:
lsl+ 有效 sensor/+ 有效 sensor/+/temperature 有效 sensor+ 无效(没有占据整个层级)
井字符号(“#” U+0023)是用于匹配主题中任意层级的通配符。多层通配符表示它的父级和任意数量的子层级,在使用多层通配符时,它必须占据整个层级并且必须是主题的最后一个字符,例如:
awk# 有效,匹配所有主题 sensor/# 有效 sensor/bedroom# 无效(没有占据整个层级) sensor/#/temperature 无效(不是主题最后一个字符)
系统主题
以
$SYS/
开头的主题为系统主题,系统主题主要用于获取 MQTT 服务器自身运行状态、消息统计、客户端上下线事件等数据。目前,MQTT 协议暂未明确规定$SYS/
主题标准,但大多数 MQTT 服务器都遵循该标准建议。
主题 说明 $SYS/brokers EMQX 集群节点列表 $SYS/brokers/[email protected]/version EMQX 版本 $SYS/brokers/[email protected]/uptime EMQX 运行时间 $SYS/brokers/[email protected]/datetime EMQX 系统时间 $SYS/brokers/[email protected]/sysdescr EMQX 系统信息 EMQX 还支持客户端上下线事件、收发流量、消息收发、系统监控等丰富的系统主题,用户可通过订阅
$SYS/#
主题获取所有系统主题消息。详细请见:EMQX 系统主题文档。共享订阅
共享订阅是 MQTT 5.0 引入的新特性,用于在多个订阅者之间实现订阅的负载均衡,MQTT 5.0 规定的共享订阅主题以
$share
开头。虽然 MQTT 协议在 5.0 版本才引入共享订阅,但是 EMQX 从 MQTT 3.1.1 版本开始就支持共享订阅。
下图中,3 个订阅者用共享订阅的方式订阅了同一个主题
$share/g/topic
,其中topic
是它们订阅的真实主题名,而$share/g/
是共享订阅前缀(g/
是群组名,可为任意 UTF-8 编码字符串)。
QoS
MQTT 提供了三种服务质量(QoS),在不同网络环境下保证消息的可靠性。
- QoS 0:消息最多传送一次。如果当前客户端不可用,它将丢失这条消息。
- QoS 1:消息至少传送一次。
- QoS 2:消息只传送一次。
关于 MQTT QoS 的更多详情,请参阅文章 MQTT QoS 0, 1, 2 介绍。
其中,使用 QoS 0 可能丢失消息,使用 QoS 1 可以保证收到消息,但消息可能重复,使用 QoS 2 可以保证消息既不丢失也不重复。QoS 等级从低到高,不仅意味着消息可靠性的提升,也意味着传输复杂程度的提升。
QoS 等级是由发布者在 PUBLISH 报文中指定的,大部分情况下 Broker 向订阅者转发消息时都会维持原始的 QoS 不变。不过也有一些例外的情况,根据订阅者的订阅要求,消息的 QoS 等级可能会在转发的时候发生降级。
QoS 0 - 最多交付一次
QoS 0 是最低的 QoS 等级。QoS 0 消息即发即弃,不需要等待确认,不需要存储和重传,因此对于接收方来说,永远都不需要担心收到重复的消息。
为什么 QoS 0 消息会丢失?
当我们使用 QoS 0 传递消息时,消息的可靠性完全依赖于底层的 TCP 协议。
而 TCP 只能保证在连接稳定不关闭的情况下消息的可靠到达,一旦出现连接关闭、重置,仍有可能丢失当前处于网络链路或操作系统底层缓冲区中的消息。这也是 QoS 0 消息最主要的丢失场景。
QoS 1 - 至少交付一次
为了保证消息到达,QoS 1 加入了应答与重传机制,发送方只有在收到接收方的 PUBACK 报文以后,才能认为消息投递成功,在此之前,发送方需要存储该 PUBLISH 报文以便下次重传。
QoS 1 需要在 PUBLISH 报文中设置 Packet ID,而作为响应的 PUBACK 报文,则会使用与 PUBLISH 报文相同的 Packet ID,以便发送方收到后删除正确的 PUBLISH 报文缓存。
为什么 QoS 1 消息会重复?
对于发送方来说,没收到 PUBACK 报文分为以下两种情况:
- PUBLISH 未到达接收方
- PUBLISH 已经到达接收方,接收方的 PUBACK 报文还未到达发送方
在第一种情况下,发送方虽然重传了 PUBLISH 报文,但是对于接收方来说,实际上仍然仅收到了一次消息。
但是在第二种情况下,在发送方重传时,接收方已经收到过了这个 PUBLISH 报文,这就导致接收方将收到重复的消息。
虽然重传时 PUBLISH 报文中的 DUP 标志会被设置为 1,用以表示这是一个重传的报文。但是接收方并不能因此假定自己曾经接收过这个消息,仍然需要将其视作一个全新的消息。
这是因为对于接收方来说,可能存在以下两种情况:
第一种情况,发送方由于没有收到 PUBACK 报文而重传了 PUBLISH 报文。此时,接收方收到的前后两个 PUBLISH 报文使用了相同的 Packet ID,并且第二个 PUBLISH 报文的 DUP 标志为 1,此时它确实是一个重复的消息。
第二种情况,第一个 PUBLISH 报文已经完成了投递,1024 这个 Packet ID 重新变为可用状态。发送方使用这个 Packet ID 发送了一个全新的 PUBLISH 报文,但这一次报文未能到达对端,所以发送方后续重传了这个 PUBLISH 报文。这就使得虽然接收方收到的第二个 PUBLISH 报文同样是相同的 Packet ID,并且 DUP 为 1,但确实是一个全新的消息。
由于我们无法区分这两种情况,所以只能让接收方将这些 PUBLISH 报文都当作全新的消息来处理。因此当我们使用 QoS 1 时,消息的重复在协议层面上是无法避免的。
甚至在比较极端的情况下,例如 Broker 从发布方收到了重复的 PUBLISH 报文,而在将这些报文转发给订阅方的过程中,再次发生重传,这将导致订阅方最终收到更多的重复消息。
在下图表示的例子中,虽然发布者的本意只是发布一条消息,但对接收方来说,最终却收到了三条相同的消息:
以上,就是 QoS 1 保证消息到达带来的副作用。
QoS 2 - 只交付一次
QoS 2 解决了 QoS 0、1 消息可能丢失或者重复的问题,但相应地,它也带来了最复杂的交互流程和最高的开销。每一次的 QoS 2 消息投递,都要求发送方与接收方进行至少两次请求/响应流程。
- 首先,发送方存储并发送 QoS 为 2 的 PUBLISH 报文以启动一次 QoS 2 消息的传输,然后等待接收方回复 PUBREC 报文。这一部分与 QoS 1 基本一致,只是响应报文从 PUBACK 变成了 PUBREC。
- 当发送方收到 PUBREC 报文,即可确认对端已经收到了 PUBLISH 报文,发送方将不再需要重传这个报文,并且也不能再重传这个报文。所以此时发送方可以删除本地存储的 PUBLISH 报文,然后发送一个 PUBREL 报文,通知对端自己准备将本次使用的 Packet ID 标记为可用了。与 PUBLISH 报文一样,我们需要确保 PUBREL 报文到达对端,所以也需要一个响应报文,并且这个 PUBREL 报文需要被存储下来以便后续重传。
- 当接收方收到 PUBREL 报文,也可以确认在这一次的传输流程中不会再有重传的 PUBLISH 报文到达,因此回复 PUBCOMP 报文表示自己也准备好将当前的 Packet ID 用于新的消息了。
- 当发送方收到 PUBCOMP 报文,这一次的 QoS 2 消息传输就算正式完成了。在这之后,发送方可以再次使用当前的 Packet ID 发送新的消息,而接收方再次收到使用这个 Packet ID 的 PUBLISH 报文时,也会将它视为一个全新的消息。
为什么 QoS 2 消息不会重复?
QoS 2 消息保证不会丢失的逻辑与 QoS 1 相同,所以这里我们就不再重复了。
与 QoS 1 相比,QoS 2 新增了 PUBREL 报文和 PUBCOMP 报文的流程,也正是这个新增的流程带来了消息不会重复的保证。
在我们更进一步之前,我们先快速回顾一下 QoS 1 消息无法避免重复的原因。
当我们使用 QoS 1 消息时,对接收方来说,回复完 PUBACK 这个响应报文以后 Packet ID 就重新可用了,也不管响应是否确实已经到达了发送方。所以就无法得知之后到达的,携带了相同 Packet ID 的 PUBLISH 报文,到底是发送方因为没有收到响应而重传的,还是发送方因为收到了响应所以重新使用了这个 Packet ID 发送了一个全新的消息。
所以,消息去重的关键就在于,通信双方如何正确地同步释放 Packet ID,换句话说,不管发送方是重传消息还是发布新消息,一定是和对端达成共识了的。
而 QoS 2 中增加的 PUBREL 流程,正是提供了帮助通信双方协商 Packet ID 何时可以重用的能力。
QoS 2 规定,发送方只有在收到 PUBREC 报文之前可以重传 PUBLISH 报文。一旦收到 PUBREC 报文并发出 PUBREL 报文,发送方就进入了 Packet ID 释放流程,不可以再使用当前 Packet ID 重传 PUBLISH 报文。同时,在收到对端回复的 PUBCOMP 报文确认双方都完成 Packet ID 释放之前,也不可以使用当前 Packet ID 发送新的消息。
因此,对于接收方来说,能够以 PUBREL 报文为界限,凡是在 PUBREL 报文之前到达的 PUBLISH 报文,都必然是重复的消息;而凡是在 PUBREL 报文之后到达的 PUBLISH 报文,都必然是全新的消息。
一旦有了这个前提,我们就能够在协议层面完成 QoS 2 消息的去重。
协议使用
https://mcxiaoke.gitbooks.io/mqtt-cn/content/mqtt/0301-CONNECT.html
连接服务器
实际连接的时候只可以发送一次CONNECT请求
有效载荷包含一个或多个编码的字段。包括客户端的唯一标识符,Will主题,Will消息,用户名和密码。除了客户端标识之外,其它的字段都是可选的,基于标志位来决定可变报头中是否需要包含这些字段。
剩余长度等于可变报头的长度(10字节)加上有效载荷的长度。
可变报头
包含四个字段:协议名(Protocol Name),协议级别(Protocol Level),连接标志(Connect Flags)和保持连接(Keep Alive)。
- 协议名
这一个名字不对的时候, 可以断开连接
- 协议级别
对于3.1.1版协议,协议级别字段的值是4(0x04)。如果发现不支持的协议级别,服务端必须给发送一个返回码为0x01(不支持的协议级别)的CONNACK报文响应CONNECT报文,然后断开客户端的连接
- 连接标志
包含一些用于指定MQTT连接行为的参数。它还指出有效载荷中的字段是否存在。
Clean Session: 清理会话, 标志被设置为1,客户端和服务端必须丢弃之前的任何会话并开始一个新的会话。会话仅持续和网络连接同样长的时间。与这个会话关联的状态数据不能被任何之后的会话重用
标志被设置为0,服务端必须基于当前会话(使用客户端标识符识别)的状态恢复与客户端的通信。如果没有与这个客户端标识符关联的会话,服务端必须创建一个新的会话。在连接断开之后,当连接断开后,客户端和服务端必须保存会话信息 [MQTT-3.1.2-4]。当清理会话标志为0的会话连接断开之后,服务端必须将之后的QoS 1和QoS 2级别的消息保存为会话状态的一部分,如果这些消息匹配断开连接时客户端的任何订阅 [MQTT-3.1.2-5]。服务端也可以保存满足相同条件的QoS 0级别的消息。
客户端的会话状态包括:
- 已经发送给服务端,但是还没有完成确认的QoS 1和QoS 2级别的消息
- 已从服务端接收,但是还没有完成确认的QoS 2级别的消息。
服务端的会话状态包括:
- 会话是否存在,即使会话状态的其它部分都是空。
- 客户端的订阅信息。
- 已经发送给客户端,但是还没有完成确认的QoS 1和QoS 2级别的消息。
- 即将传输给客户端的QoS 1和QoS 2级别的消息。
- 已从客户端接收,但是还没有完成确认的QoS 2级别的消息。
- 可选,准备发送给客户端的QoS 0级别的消息。
这个选择取决于具体的应用。清理会话标志设置为1的客户端不会收到旧的应用消息,而且在每次连接成功后都需要重新订阅任何相关的主题。清理会话标志设置为0的客户端会收到所有在它连接断开期间发布的QoS 1和QoS 2级别的消息。因此,要确保不丢失连接断开期间的消息,需要使用QoS 1或 QoS 2级别,同时将清理会话标志设置为0。
Will Flag: 被设置为1,表示如果连接请求被接受了,遗嘱(Will Message)消息必须被存储在服务端并且与这个网络连接关联。之后网络连接关闭时,服务端必须发布这个遗嘱消息,除非服务端收到DISCONNECT报文时删除了这个遗嘱消息
- 服务端检测到了一个I/O错误或者网络故障。
- 客户端在保持连接(Keep Alive)的时间内未能通讯。
- 客户端没有先发送DISCONNECT报文直接关闭了网络连接。
- 由于协议错误服务端关闭了网络连接。
如果遗嘱标志被设置为1,连接标志中的Will QoS和Will Retain字段会被服务端用到,同时有效载荷中必须包含Will Topic和Will Message字段
如果遗嘱标志被设置为0,连接标志中的Will QoS和Will Retain字段必须设置为0,并且有效载荷中不能包含Will Topic和Will Message字段
如果遗嘱标志被设置为0,网络连接断开时,不能发送遗嘱消息, 服务端应该迅速发布遗嘱消息。在关机或故障的情况下,服务端可以推迟遗嘱消息的发布直到之后的重启。如果发生了这种情况,在服务器故障和遗嘱消息被发布之间可能会有一个延迟
QoS Will QoS: 这两位用于指定发布遗嘱消息时使用的服务质量等级
如果遗嘱标志被设置为1,遗嘱QoS的值可以等于0(0x00),1(0x01),2(0x02)。它的值不能等于3
Will Retain: 遗嘱保留, 如果遗嘱消息被发布时需要保留,需要指定这一位的值。
User Name Flag: 如果用户名(User Name)标志被设置为0,有效载荷中不能包含用户名字段。如果用户名(User Name)标志被设置为1,有效载荷中必须包含用户名字段。
Password Flag: 标志被设置为0,有效载荷中不能包含密码字段, 标志被设置为1,有效载荷中必须包含密码字段
Keep Alive: 是一个以秒为单位的时间间隔,表示为一个16位的字,它是指在客户端传输完成一个控制报文的时刻到发送下一个报文的时刻,两者之间允许空闲的最大时间间隔。客户端负责保证控制报文发送的时间间隔不超过保持连接的值。如果没有任何其它的控制报文可以发送,客户端必须发送一个PINGREQ报文
不管保持连接的值是多少,客户端任何时候都可以发送PINGREQ报文,并且使用PINGRESP报文判断网络和服务端的活动状态。
如果保持连接的值非零,并且服务端在一点五倍的保持连接时间内没有收到客户端的控制报文,它必须断开客户端的网络连接,认为网络连接已断开
客户端发送了PINGREQ报文之后,如果在合理的时间内仍没有收到PINGRESP报文,它应该关闭到服务端的网络连接。
保持连接的值为零表示关闭保持连接功能。这意味着,服务端不需要因为客户端不活跃而断开连接。注意:不管保持连接的值是多少,任何时候,只要服务端认为客户端是不活跃或无响应的,可以断开客户端的连接。
有效荷载
CONNECT报文的有效载荷(payload)包含一个或多个以长度为前缀的字段,可变报头中的标志决定是否包含这些字段。如果包含的话,必须按这个顺序出现:客户端标识符,遗嘱主题,遗嘱消息,用户名,密码
- 客户端标识符
服务端使用客户端标识符 (ClientId) 识别客户端。连接服务端的每个客户端都有唯一的客户端标识符(ClientId)。客户端和服务端都必须使用ClientId识别两者之间的MQTT会话相关的状态
服务端必须允许1到23个字节长的UTF-8编码的客户端标识符,客户端标识符只能包含这些字符:“0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”(大写字母,小写字母和数字)
服务端可以允许编码后超过23个字节的客户端标识符 (ClientId)。服务端可以允许包含不是上面列表字符的客户端标识符 (ClientId)。
服务端可以允许客户端提供一个零字节的客户端标识符 (ClientId) ,如果这样做了,服务端必须将这看作特殊情况并分配唯一的客户端标识符给那个客户端。然后它必须假设客户端提供了那个唯一的客户端标识符,正常处理这个CONNECT报文
如果客户端提供了一个零字节的客户端标识符,它必须同时将清理会话标志设置为1
如果客户端提供的ClientId为零字节且清理会话标志为0,服务端必须发送返回码为0x02(表示标识符不合格)的CONNACK报文响应客户端的CONNECT报文,然后关闭网络连接
- Will Topic
遗嘱主题, 如果遗嘱标志被设置为1,有效载荷的下一个字段是遗嘱主题
- Will MEssage
如果遗嘱标志被设置为1,有效载荷的下一个字段是遗嘱消息。遗嘱消息定义了将被发布到遗嘱主题的应用消息
这个字段由一个两字节的长度和遗嘱消息的有效载荷组成,表示为零字节或多个字节序列。长度给出了跟在后面的数据的字节数,不包含长度字段本身占用的两个字节。
- User Name用户名
如果用户名(User Name)标志被设置为1,有效载荷的下一个字段就是它。
- Password
如果密码(Password)标志被设置为1,有效载荷的下一个字段就是它。密码字段包含一个两字节的长度字段,长度表示二进制数据的字节数(不包含长度字段本身占用的两个字节),后面跟着0到65535字节的二进制数据
响应
- 网络连接建立后,如果服务端在合理的时间内没有收到CONNECT报文,服务端应该关闭这个连接。
- 服务端必须按照3.1节的要求验证CONNECT报文,如果报文不符合规范,服务端不发送CONNACK报文直接关闭网络连接 [MQTT-3.1.4-1]。
- 服务端可以检查CONNECT报文的内容是不是满足任何进一步的限制,可以执行身份验证和授权检查。如果任何一项检查没通过,按照3.2节的描述,它应该发送一个适当的、返回码非零的CONNACK响应,并且必须关闭这个网络连接。
- 如果ClientId表明客户端已经连接到这个服务端,那么服务端必须断开原有的客户端连接 [MQTT-3.1.4-2]。
- 服务端必须按照 3.1.2.4节的描述执行清理会话的过程 [MQTT-3.1.4-3]。
- 服务端必须发送返回码为零的CONNACK报文作为CONNECT报文的确认响应 [MQTT-3.1.4-4]。
- 开始消息分发和保持连接状态监视。
允许客户端在发送CONNECT报文之后立即发送其它的控制报文;客户端不需要等待服务端的CONNACK报文。如果服务端拒绝了CONNECT,它不能处理客户端在CONNECT报文之后发送的任何数据
客户端通常会等待一个CONNACK报文。然而客户端有权在收到CONNACK之前发送控制报文,由于不需要维持连接状态,这可以简化客户端的实现。
确认连接
服务端发送CONNACK报文响应从客户端收到的CONNECT报文。服务端发送给客户端的第一个报文必须是CONNACK
当前会话
第1个字节是 连接确认标志,位7-1是保留位且必须设置为0。 第0 (SP)位 是当前会话(Session Present)标志。
如果服务端收到清理会话(CleanSession)标志为1的连接,除了将CONNACK报文中的返回码设置为0之外,还必须将CONNACK报文中的当前会话设置(Session Present)标志为0
如果服务端收到一个CleanSession为0的连接,当前会话标志的值取决于服务端是否已经保存了ClientId对应客户端的会话状态。如果服务端已经保存了会话状态,它必须将CONNACK报文中的当前会话标志设置为1。如果服务端没有已保存的会话状态,它必须将CONNACK报文中的当前会话设置为0。还需要将CONNACK报文中的返回码设置为0
一旦完成了会话的初始化设置,已经保存会话状态的客户端将期望服务端维持它存储的会话状态。如果客户端从服务端收到的当前的值与预期的不同,客户端可以选择继续这个会话或者断开连接。客户端可以丢弃客户端和服务端之间的会话状态,方法是,断开连接,将清理会话标志设置为1,再次连接,然后再次断开连接。
如果服务端发送了一个包含非零返回码的CONNACK报文,它必须将当前会话标志设置为0
返回码
值 | 返回码响应 | 描述 |
---|---|---|
0 | 0x00连接已接受 | 连接已被服务端接受 |
1 | 0x01连接已拒绝,不支持的协议版本 | 服务端不支持客户端请求的MQTT协议级别 |
2 | 0x02连接已拒绝,不合格的客户端标识符 | 客户端标识符是正确的UTF-8编码,但服务端不允许使用 |
3 | 0x03连接已拒绝,服务端不可用 | 网络连接已建立,但MQTT服务不可用 |
4 | 0x04连接已拒绝,无效的用户名或密码 | 用户名或密码的数据格式无效 |
5 | 0x05连接已拒绝,未授权 | 客户端未被授权连接到此服务器 |
6-255 | 保留 |
发布消息
- DUP
如果DUP标志被设置为0,表示这是客户端或服务端第一次请求发送这个PUBLISH报文。如果DUP标志被设置为1,表示这可能是一个早前报文请求的重发。
客户端或服务端请求重发一个PUBLISH报文时,必须将DUP标志设置为1 。对于QoS 0的消息,DUP标志必须设置为0
服务端发送PUBLISH报文给订阅者时,收到(入站)的PUBLISH报文的DUP标志的值不会被传播。发送(出站)的PUBLISH报文与收到(入站)的PUBLISH报文中的DUP标志是独立设置的,它的值必须单独的根据发送(出站)的PUBLISH报文是否是一个重发来确定
- QoS
PUBLISH报文不能将QoS所有的位设置为1。如果服务端或客户端收到QoS所有位都为1的PUBLISH报文,它必须关闭网络连接
- RETAIN
如果客户端发给服务端的PUBLISH报文的保留(RETAIN)标志被设置为1,服务端必须存储这个应用消息和它的服务质量等级(QoS),以便它可以被分发给未来的主题名匹配的订阅者 [MQTT-3.3.1-5]。一个新的订阅建立时,对每个匹配的主题名,如果存在最近保留的消息,它必须被发送给这个订阅者
如果服务端收到一条保留(RETAIN)标志为1的QoS 0消息,它必须丢弃之前为那个主题保留的任何消息。它应该将这个新的QoS 0消息当作那个主题的新保留消息,但是任何时候都可以选择丢弃它 — 如果这种情况发生了,那个主题将没有保留消息
服务端发送PUBLISH报文给客户端时,如果消息是作为客户端一个新订阅的结果发送,它必须将报文的保留标志设为1。当一个PUBLISH报文发送给客户端是因为匹配一个已建立的订阅时,服务端必须将保留标志设为0,不管它收到的这个消息中保留标志的值是多少
保留标志为1且有效载荷为零字节的PUBLISH报文会被服务端当作正常消息处理,它会被发送给订阅主题匹配的客户端。此外,同一个主题下任何现存的保留消息必须被移除,因此这个主题之后的任何订阅者都不会收到一个保留消息 [MQTT-3.3.1-10]。当作正常 意思是现存的客户端收到的消息中保留标志未被设置。服务端不能存储零字节的保留消息 [MQTT-3.3.1-11]。
如果客户端发给服务端的PUBLISH报文的保留标志位0,服务端不能存储这个消息也不能移除或替换任何现存的保留消息
可变报头
可变报头按顺序包含主题名和报文标识符。
- 主题名
主题名(Topic Name)用于识别有效载荷数据应该被发布到哪一个信息通道。
主题名必须是PUBLISH报文可变报头的第一个字段。它必须是 1.5.3节定义的UTF-8编码的字符串 [MQTT-3.3.2-1]。
PUBLISH报文中的主题名不能包含通配符。
服务端发送给订阅客户端的PUBLISH报文的主题名必须匹配该订阅的主题过滤器
- 报文标识符
只有当QoS等级是1或2时,报文标识符(Packet Identifier)字段才能出现在PUBLISH报文中。
有效数据
有效载荷包含将被发布的应用消息。数据的内容和格式是应用特定的。有效载荷的长度这样计算:用固定报头中的剩余长度字段的值减去可变报头的长度。包含零长度有效载荷的PUBLISH报文是合法的
响应
发布确认Qos1
PUBACK报文是对QoS 1等级的PUBLISH报文的响应。
之后是确认的报文的标识符
发布收到Qos2--1
PUBREC报文是对QoS等级2的PUBLISH报文的响应。它是QoS 2等级协议交换的第二个报文。
发布释放Qos2--2
PUBREL报文是对PUBREC报文的响应。它是QoS 2等级协议交换的第三个报文。
可变报头包含与等待确认的PUBREC报文相同的报文标识符。
发布完成Qos2--3
PUBCOMP报文是对PUBREL报文的响应。它是QoS 2等级协议交换的第四个也是最后一个报文。
订阅主题
客户端向服务端发送SUBSCRIBE报文用于创建一个或多个订阅。每个订阅注册客户端关心的一个或多个主题。为了将应用消息转发给与那些订阅匹配的主题,服务端发送PUBLISH报文给客户端。SUBSCRIBE报文也(为每个订阅)指定了最大的QoS等级,服务端根据这个发送应用消息给客户端。
之后是一个报文标识符, 然后是有效载荷, 里面是主题过滤器表
表里面是主题过滤器以及一个字节的服务质量要求
长度不包括这一个服务质量要求
示例
响应
服务端收到客户端发送的一个SUBSCRIBE报文时,必须使用SUBACK报文响应
如果服务端收到一个SUBSCRIBE报文,报文的主题过滤器与一个现存订阅的主题过滤器相同,那么必须使用新的订阅彻底替换现存的订阅。新订阅的主题过滤器和之前订阅的相同,但是它的最大QoS值可以不同。与这个主题过滤器匹配的任何现存的保留消息必须被重发,但是发布流程不能中断
服务端发送给客户端的SUBACK报文对每一对主题过滤器 和QoS等级都必须包含一个返回码。这个返回码必须表示那个订阅被授予的最大QoS等级,或者表示这个订阅失败。服务端可以授予比订阅者要求的低一些的QoS等级。为响应订阅而发出的消息的有效载荷的QoS必须是原始发布消息的QoS和服务端授予的QoS两者中的最小值。如果原始消息的QoS是1而被授予的最大QoS是0,允许服务端重复发送一个消息的副本给订阅者
对某个特定的主题过滤器,如果正在订阅的客户端被授予的最大QoS等级是1,那么匹配这个过滤器的QoS等级0的应用消息会按QoS等级0分发给这个客户端。这意味着客户端最多收到这个消息的一个副本。从另一方面说,发布给同一主题的QoS等级2的消息会被服务端降级到QoS等级1再分发给客户端,因此客户端可能会收到重复的消息副本。
如果正在订阅的客户端被授予的最大QoS等级是0,那么原来按QoS等级2发布给客户端的应用消息在繁忙时可能会丢失,但是服务端不应该发送重复的消息副本。发布给同一主题的 QoS等级1的消息在传输给客户端时可能会丢失或重复。
使用QoS等级2订阅一个主题过滤器等于是说:我想要按照它们发布时的QoS等级接受匹配这个过滤器的消息 。这意味着,确定消息分发时可能的最大QoS等级是发布者的责任,而订阅者可以要求服务端降低QoS到更适合它的等级。
订阅确认
服务端发送SUBACK报文给客户端,用于确认它已收到并且正在处理SUBSCRIBE报文。
SUBACK报文包含一个返回码清单,它们指定了SUBSCRIBE请求的每个订阅被授予的最大QoS等级。
之后是回复SUBSCRIBE的报文标识符
有效载荷包含一个返回码清单。每个返回码对应等待确认的SUBSCRIBE报文中的一个主题过滤器。返回码的顺序必须和SUBSCRIBE报文中主题过滤器的顺序相同
- 0x00 - 最大QoS 0
- 0x01 - 成功 – 最大QoS 1
- 0x02 - 成功 – 最大 QoS 2
- 0x80 - Failure 失败
取消订阅
客户端发送UNSUBSCRIBE报文给服务端,用于取消订阅主题。
之后是一个报文标识符
UNSUBSCRIBE报文的有效载荷包含客户端想要取消订阅的主题过滤器列表。UNSUBSCRIBE报文中的主题过滤器必须是连续打包的
这一个车只有完全匹配的时候才会进行删除
- 它必须停止分发任何新消息给这个客户端 [MQTT-3.10.4-2]。
- 它必须完成分发任何已经开始往客户端发送的QoS 1和QoS 2的消息 [MQTT-3.10.4-3]。
- 它可以继续发送任何现存的准备分发给客户端的缓存消息。
服务端必须发送UNSUBACK报文响应客户端的UNSUBSCRIBE请求。UNSUBACK报文必须包含和UNSUBSCRIBE报文相同的报文标识符 [MQTT-3.10.4-4]。即使没有删除任何主题订阅,服务端也必须发送一个UNSUBACK响应 [MQTT-3.10.4-5]。
取消订阅确认
之后是重复标识符, 没有其他数据
心跳请求
客户端发送PINGREQ报文给服务端的。用于:
- 在没有任何其它控制报文从客户端发给服务的时,告知服务端客户端还活着。
- 请求服务端发送 响应确认它还活着。
- 使用网络以确认网络连接没有断开。
心跳响应
服务端发送PINGRESP报文响应客户端的PINGREQ报文。表示服务端还活着。
断开连接
DISCONNECT报文是客户端发给服务端的最后一个控制报文。表示客户端正常断开连接。