上一节我们解释了发布/订阅模式的工作原理以及它是如何在 MQTT 中应用的。
以下是核心概念的快速回顾:
-
发布/订阅将发送消息的客户端(发布者)与接收消息的客户端(订阅者)分离。
-
MQTT 使用消息的主题(主题)来确定哪个消息发送到哪个客户端(订阅者)。主题是一个分层结构的字符串,可用于过滤和路由消息
我们的上一篇文章为您提供了发布/订阅模型的高级视图,以及它与传统消息队列的不同之处。这篇文章采用了一种实用的方法,并包含了有关 MQTT 的基本知识:术语 MQTT 客户端和代理的定义、MQTT 连接的基础知识、连接消息及其参数,以及通过代理的确认建立连接。
MQTT Client 和 MQTT 代理 简介
因为 MQTT 将发布者与订阅者分离,所以客户端连接始终由代理处理。在深入了解这些连接的细节之前,让我们先弄清楚客户端和代理的含义。
客户(Client) 当我们谈论客户端时,我们几乎总是指MQTT 客户端。发布者和订阅者都是 MQTT 客户端。发布者和订阅者标签是指客户端当前是发布消息还是订阅接收消息(发布和订阅功能也可以在同一个 MQTT 客户端中实现)。MQTT 客户端是运行 MQTT 库并通过网络连接到MQTT 代理的任何设备(从微控制器到成熟的服务器)。例如,MQTT 客户端可以是一个非常小的、资源受限的设备,它通过无线网络连接,并具有一个最低限度的库。MQTT 客户端也可以是运行图形 MQTT 客户端以进行测试的典型计算机。基本上,任何通过 TCP/IP 堆栈使用 MQTT 的设备都可以称为 MQTT 客户端。MQTT 协议的客户端实现非常直接和精简。易于实施是 MQTT 非常适合小型设备的原因之一。MQTT 客户端库可用于多种编程语言。例如,Android、Arduino、C、C++、C#、Go、iOS、Java、JavaScript 和 .NET。
经纪人(Broke) MQTT 客户端的对应物是 MQTT 代理。代理是任何发布/订阅协议的核心。根据实现的不同,代理可以处理多达数百万个并发连接的 MQTT 客户端。
代理负责接收所有消息,过滤消息,确定每条消息的订阅者,并将消息发送给这些订阅的客户端。代理还保存所有具有持久会话的客户端的会话数据,包括订阅和错过的消息(更多详细信息)。代理的另一个职责是客户端的身份验证和授权。通常,代理是可扩展的,这有助于自定义身份验证、授权和集成到后端系统。集成尤为重要,因为代理经常是直接暴露在互联网上的组件,处理大量客户端,需要将消息传递给下游分析和处理系统。正如在以前的帖子,订阅所有消息并不是一个真正的选择。简而言之,代理是每条消息都必须通过的中心枢纽。因此,您的代理必须具有高度可扩展性、可集成到后端系统、易于监控并且(当然)具有抗故障能力,这一点很重要。
MQTT 连接
MQTT 协议基于 TCP/IP。客户端和代理都需要有一个 TCP/IP 堆栈。
MQTT 连接始终在一个客户端和代理之间。客户端从不直接相互连接。为了启动连接,客户端向代理发送 CONNECT 消息。代理以 CONNACK 消息和状态代码进行响应。建立连接后,代理将保持打开状态,直到客户端发送断开连接命令或连接中断。
通过 NAT 的 MQTT 连接
在许多常见用例中,MQTT 客户端位于路由器后面,该路由器使用网络地址转换 (NAT) 将专用网络地址(如 192.168.xx、10.0.xx)转换为面向公众的地址。正如我们已经提到的,MQTT 客户端通过向代理发送 CONNECT 消息来启动连接。因为代理有一个公共地址并保持连接打开以允许双向发送和接收消息(在初始 CONNECT 之后),所以位于 NAT 后面的客户端完全没有问题。
客户端使用 CONNECT 消息发起连接
现在让我们看一下MQTT CONNECT命令消息。为了启动连接,客户端向代理发送命令消息。如果此 CONNECT 消息格式不正确(根据 MQTT 规范)或在打开网络套接字和发送连接消息之间经过了太多时间,则代理将关闭连接。此行为阻止了可能减慢代理速度的恶意客户端。 一个正常的 MQTT 3 客户端发送一条包含以下内容:
CONNECT 消息中包含的一些信息对于 MQTT 库的实现者可能比该库的用户更感兴趣。有关所有详细信息,请查看MQTT 3.1.1 规范。
我们将重点关注以下选项:
客户 ID(client Id)
客户端标识符 (ClientId)标识连接到 MQTT 代理的每个 MQTT 客户端。代理使用 ClientId 来标识客户端和客户端的当前状态。因此,此 Id 对于每个客户端和代理应该是唯一的。在 MQTT 3.1.1 中,如果您不需要代理持有状态,您可以发送一个空的 ClientId。空的 ClientId 导致没有任何状态的连接。在这种情况下,必须将 clean session 标志设置为 true,否则代理将拒绝连接。
清除会话(cleanSession)
干净会话标志告诉代理客户端是否要建立持久会话。在持久会话中 (CleanSession = false),代理存储客户端的所有订阅以及订阅服务质量 (QoS)级别 1 或 2 的客户端的所有错过消息。如果会话不是持久的 (CleanSession = true ),代理不会为客户端存储任何内容,并从任何先前的持久会话中清除所有信息。
用户名密码(username/password)
MQTT 可以发送用户名和密码进行客户端认证和授权。但是,如果此信息未加密或散列(通过实现或 TLS),则密码将以纯文本形式发送。我们强烈建议将用户名和密码与安全传输一起使用。当使用HiveMQ 这样的代理可以使用 SSL 证书对客户端进行身份验证,因此不需要用户名和密码。
遗嘱留言(last will)
最后遗嘱消息是 MQTT 的遗嘱和遗嘱 (LWT) 功能的一部分。当客户端异常断开连接时,此消息会通知其他客户端。当客户端连接时,它可以以 MQTT 消息和 CONNECT 消息中的主题的形式向代理提供最后的遗嘱。如果客户端不正常断开连接,代理会代表客户端发送 LWT 消息。
保持连接(keep Alive)
保持活动是客户端指定并在连接建立时与代理通信的时间间隔(以秒为单位)。这个间隔定义了代理和客户端可以忍受的最长时间而不发送消息。客户端承诺向代理发送常规 PING 请求消息。代理以 PING 响应进行响应。此方法允许双方确定另一方是否仍然可用。
基本上,这就是从 MQTT 3.1.1 客户端连接到 MQTT 代理所需的全部信息。各个库通常具有您可以配置的其他选项。例如,队列消息在特定实现中的存储方式。
带有 CONNACK 消息的代理响应
当代理收到 CONNECT 消息时,它有义务以 CONNACK 消息进行响应。
CONNACK 消息包含两个数据条目:
-
会话呈现标志
-
连接返回码
会话存在标志
会话存在标志告诉客户端代理是否已经有一个可从先前与客户端的交互中获得的持久会话。当客户端连接并将 Clean Session 设置为 true 时,会话存在标志始终为 false,因为没有可用的会话。如果客户端在 Clean Session 设置为 false 的情况下连接,则有两种可能性: 如果会话信息可用于 clientId。并且代理已经存储了会话信息,会话存在标志为真。否则,如果代理没有 clientId 的任何会话信息,则会话存在标志为 false。此标志是在 MQTT 3.1.1 中添加的,以帮助客户端确定他们是否需要订阅主题,或者主题是否仍存储在持久会话中。
连接返回码
CONNACK消息中的第二个标志 是连接确认标志。这个标志包含一个返回码,告诉客户端连接尝试是否成功。
以下是返回代码一目了然:。
返回码 | 返回码响应 |
---|---|
0 | 已接受连接 |
1 | 连接被拒绝,协议版本不可接受 |
2 | 连接被拒绝,标识符被拒绝 |
3 | 连接被拒绝,服务器不可用 |
4 | 连接被拒绝,用户名或密码错误 |
5 | 连接被拒绝,未授权 |
有关每个代码的更详细说明,请参阅MQTT 规范。