后续:【发布订阅模式】基于Paho java的MQTT通信实战-搭建客户端【下】
MQTT 简介
MQTT是一个极其轻量级的发布/订阅消息传输协议,适用于网络带宽较低的场合.
它有一个用来指定消息类型的简单标头,有一个基于文本的主题,还有一个任意的二进制有效负载。应用程序可对有效负载采用任何数据格式,比如 JSON、XML、加密二进制或 Base64,只要目标客户端能够解析该有效负载
MQTT 协议基于 TCP/IP 协议,MQTT Broker 和 Client 都有需要有 TCP/IP 地址。
它通过一个代理服务器(broker),任何一个客户端(client)都可以订阅或者发布某个主题的消息,然后订阅了该主题的客户端则会收到该消息。
客户端连接到代理。它可以订阅代理中的任何消息“主题”。此连接可以是简单的 TCP/IP 连接,也可以是用于发送敏感消息的加密 TLS 连接。
客户端通过将消息和主题发送给代理,发布某个主题范围内的消息。
代理然后将消息转发给所有订阅该主题的客户端。
设计原则
MQTT遵循以下设计原则:
精简,不添加可有可无的功能。
发布/订阅(Pub/Sub)模式,方便消息在传感器之间传递。
允许用户动态创建主题,零运维成本。
把传输量降到最低以提高传输效率。
把低带宽、高延迟、不稳定的网络等因素考虑在内。
支持连续的会话控制。
提供服务质量管理。
假设数据不可知,不强求传输数据的类型与格式,保持灵活性。
发布订阅模式是传统 Client/Server 模式的一种解耦方案。
发布者通过 Broker 与消费者之间通信,Broker 的作用是将收到的消息通过某种过滤规则,正确地发送给消费者。发布/订阅模式 相对于 客户端/服务器模式 的好处在于:
发布者和消费者之间不必预先知道对方的存在,比如不需要预先沟通对方的 IP Address 和 Port;发布者和消费者之间不必同时运行。因为 Broker 是一直运行的。
在MQTT协议里,过滤规则==Topic
主题名(Topic Name)用于识别消息应该被发布到哪一个会话,服务端发送给订阅客户端的 Publish 报文的主题名必须匹配该订阅的主题过滤器。Topic是不要预先创建的,发布者发送消息到某个主题、或者订阅者订阅某个主题的时候,Broker 就会自动创建这个主题。
Topic有层级结构,并且支持通配符+和#:
- “+” 是匹配单层的通配符。比如 news/+ 可以匹配 news/sports,news/+/basketball 可匹配到 news/sports/basketball。
- “#” 是一到多层的通配符。比如 news/# 可以匹配 news、 news/sports、news/sports/basketball 以及 news/sports/basketball/x 等等。
带宽消耗最小化
MQTT 协议将协议本身占用的额外消耗最小化,消息头部最小只需要占用 2 个字节。如 PINGREQ / PINGRESP 和 DISCONNECT 报文是不需要可变头部的,也没有 Payload,也就是说它们的报文大小仅仅消耗 2 个字节。
MQTT 的消息格式分三部分:
固定长度头部,2 个字节,所有消息类型里都有
可变长度头部,只有某些消息类型里有
Payload,只有某些消息类型里有
三个可选的 QoS 等级
为适应设备不同的网络环境,MQTT 设计了 3 个 QoS 等级:0, 1, 2:
At most once (0)
At least once (1)
Exactly once (2)
QoS 0
是一种 “fire and forget” 的消息发送模式:Sender (可能是 Publisher 或者 Broker) 发送一条消息之后,就不再关心它有没有发送到对方,也不设置任何重发机制。
-可以接受消息偶尔丢失。
-在同一个子网内部的服务间的消息交互,或其他客户端与服务端网络非常稳定的场景。
QoS 1
包含了简单的重发机制,Sender 发送消息之后等待接收者的 ACK,如果没收到 ACK 则重新发送消息。这种模式能保证消息至少能到达一次,但无法保证消息重复。
-对系统资源消耗较为关注,希望性能最优化。
-消息不能丢失,但能接受并处理重复的消息。
QoS 2
设计了略微复杂的重发和重复消息发现机制,保证消息到达对方并且严格只到达一次。
-不能忍受消息丢失(消息的丢失会造成生命或财产的损失),且不希望收到重复的消息。
-数据完整性与及时性要求较高的银行、消防、航空等行业。
会话保持
MQTT设计了协议层的保活机制:在 CONNECT 报文里可设置 Keepalive
字段,来设置保活心跳包 PINGREQ/PINGRESP 的发送时间间隔。当长时间无法收到设备的 PINGREQ 的时候,Broker 就会认为设备已经下线。
Keepalive
有两个作用:
发现对端死亡或者网络中断
在长时间无消息交互的情况下,保持连接不被网络设备断开
对于那些想要在重新上线后,重新收到离线期间错过的消息的设备,MQTT 设计了持久化连接:在 CONNECT 报文里可设置 CleanSession 字段为 False,则 Broker 会为终端存储–
设备所有的订阅 / 还未被设备确认的 QoS1 和 QoS 消息 / 设备离线时错过的消息
在线状态感知
MQTT 设计了遗愿(Last Will) 消息,让 Broker 在发现设备异常下线的情况下,帮助设备发布一条遗愿消息到指定的主题。
实际上在某些 MQTT 服务器的实现里 (比如 EMQX),设备上线或下线的时候 Broker 会通过某些系统主题发布设备状态更新,更符合实际应用场景。
参数详解
Clean Session
客户端和服务端可以保存会话状态,以支持跨网络连接的可靠消息传输,这个标志告诉服务器这次连接是不是一个全新的连接。
客户端的会话状态包括:
已经发送给服务端,但是还没有完成确认的 QoS 1 和 QoS 2 级别的消息
已从服务端接收,但是还没有完成确认的 QoS 2 级别的消息。
服务端的会话状态包括:
会话是否存在,即使会话状态的其它部分都是空。
客户端的订阅信息。
已经发送给客户端,但是还没有完成确认的 QoS 1 和 QoS 2 级别的消息。
即将传输给客户端的 QoS 1和 QoS 2 级别的消息。
已从客户端接收,但是还没有完成确认的 QoS 2 级别的消息。
可选,准备发送给客户端的 QoS 0 级别的消息。
如果 CleanSession 标志被设置为 1,客户端和服务端必须丢弃之前的任何会话并开始一个新的会话。会话仅持续和网络连接同样长的时间。
如果 CleanSession 标志被设置为 0,服务端必须基于当前会话(使用 ClientId 识别)的状态恢复与客户端的通信。如果没有与这个客户端标识符关联的会话,服务端必须创建一个新的会话。当连接断开后,客户端和服务端必须保存会话信息。
Connack 确认连接请求[broker->client]
客户端发送 Connect 报文请求对服务器的连接,服务器必须发送 Connack 报文作为对 来自客户端的 Connect 报文的回应。如果客户端在合理的时间内没有收到服务端的CONNACK报文,客户端应该关闭网络连接。合理的时间取决于应用的类型和通信基础设施。在 MQTTX 中,可以通过 Connection Timeout 来设置合理的超时时间。
Connack 报文包含 Session Present 和 Connect Return code 两个重要的标志。
Session Present
标志表示当前会话是否是一个新的会话,如果服务端收到 CleanSession 标志为1的连接,Connack报文中的 SessionPresent 标志为 0 。如果服务端收到一个 CleanSession 为0的连接,SessionPresent 标志的值取决于服务端是否已经保存了 ClientId 对应客户端的会话状态。如果服务端已经保存了会话状态,Connack 报文中的 SessionPresent 标志为 1,如果服务端没有已保存的会话状态,Connack 报文中的 SessionPresent 标志为 0.
Connect Return code
表示服务器对此次 Connect 的回应,0 表示连接已被服务器接受。如果服务端收到一个合法的 CONNECT 报文,但出于某些原因无法处理它,服务端应该尝试发送一个包含非零返回码(表格中的某一个)的 CONNACK 报文。如果服务端发送了一个包含非零返回码的CONNACK 报文,那么它必须关闭网络连接。
Subscribe 订阅主题[client -> broker]
客户端向服务端发送 Subscribe 报文用于创建一个或多个订阅。每个订阅注册客户端关心的一个或多个主题。为了将应用消息转发给与那些订阅匹配的主题,服务端发送 Publish 报文给客户端。Subscribe 报文为每个订阅指定了最大的 QoS 等级,服务端根据这个发送应用消息给客户端。
Subscribe 报文的有效载荷必须包含至少一对主题过滤器和QoS等级字段组合。没有有效载荷的 Subscribe 报文是违反协议的。
Suback 订阅确认[broker->client]
服务端发送 Suback 报文给客户端,用于确认它已收到并且正在处理 Subscribe 报文。
Suback 报文包含一个原因码列表,用于指定授予的最大QoS等级或 Subscribe 报文所请求的每个订阅发生的错误,每个原因码对应 Subscribe 报文中的一个主题过滤器。Suback 报文中的原因码顺序必须与 Subscribe 报文中的主题过滤器顺序相匹配
允许的返回码值:
0x00 - 最大QoS 0
0x01 - 成功 – 最大QoS 1
0x02 - 成功 – 最大 QoS 2
0x80 - Failure 失败
Publish 发布消息
Publish 报文是指从客户端向服务端或者服务端向客户端传输一个应用消息,服务器收到 Publish 报文后根据主题过滤器将消息转发给其他客户端。
Publish 报文不能将 QoS 所有的位设置为 1。如果服务端或客户端收到 QoS 所有位都为 1 的 Publish 报文,它必须关闭网络连接。
如果pub和sub都是2,就是2组4次交互。如果只有pub为2,一组4次交互。但如果pub是1,sub为2,则测试结果和sub,pub都为1的情况一样,sub和broker之间不是4次交互。
Retain
如果客户端发给服务端的 Publish 报文的保留(RETAIN)标志被设置为 1,服务端必须存储这个应用消息和它的服务质量等级(QoS),以便它可以被分发给未来的主题名匹配的订阅者 。一个新的订阅建立时,对每个匹配的主题名,如果存在最近保留的消息,它必须被发送给这个订阅者。如果服务端收到一条保留(RETAIN)标志为1的Q消息,它必须丢弃之前为那个主题保留的任何消息,并将这个新的消息当作那个主题的新保留消息。
保留标志为 1 且有效载荷为零字节的 Publish 报文会被服务端当作正常消息处理,它会被发送给订阅主题匹配的客户端。此外,同一个主题下任何现存的保留消息必须被移除,因此这个主题之后的任何订阅者都不会收到一个保留消息
Payload(数据包的第三部分)
有效载荷包含将被发布的应用消息。数据的内容和格式是应用特定的,可以发送图像,任何编码的文本,加密的数据以及几乎所有二进制数据。
(1)CONNECT,消息体内容主要是:客户端的ClientID、订阅的Topic、Message以及用户名和密码。
(2)SUBSCRIBE,消息体内容是一系列的要订阅的主题以及QoS。
(3)SUBACK,消息体内容是服务器对于SUBSCRIBE所申请的主题及QoS进行确认和回复。
(4)UNSUBSCRIBE,消息体内容是要订阅的主题。