• Linux--MQTT(二)通信基本原理


    一、MQTT 通信基本原理

    MQTT 是一种基于 客户端 - 服务端 架构的消息传输协议,所以在 MQTT 协议通信中,有两个最为重要的角色,它们便是服务端 客户端
    举例:若开发板向“芯片温度”这一主题发布消息,那么服务端接收到消息后会将消息转发给订阅了“芯片温度”的所有客户端。

    服务端

    MQTT 服务端通常是一台服务器( broker ),它是 MQTT 信息传输的枢纽,负责将 MQTT 客户端发送来的信息传递给 MQTT 客户端; MQTT 服务端还负责管理 MQTT 客户端,以确保客户端之间的通讯顺畅, 保证 MQTT 信息得以正确接收和准确投递。

    客户端

    MQTT 客户端可以向服务端发布信息,也可以从服务端收取信息;我们把客户端发送信息的行为称为 “发布”信息。而客户端要想从服务端收取信息,则首先要向服务端“订阅”信息。“订阅”信息这一操作很像我们在使用微信时“关注”了某个公众号,当公众号的作者发布新的文章时,微信官方会向关注了该公众号的所有用户发送信息,告诉他们有新文章更新了,以便用户查看。

    MQTT 主题

    客户端想要从服务器获取信息,首先需要订阅信息,那客户端如何订阅信息呢?这里我们要引入“主题(Topic )”的概念,“主题”在 MQTT 通信中是一个非常重要的概念,客户端发布信息以及订阅信息都是围绕“主题”来进行的,并且 MQTT 服务端在管理 MQTT 信息时,也是使用“主题”来控制的。
    客户端发布消息时需要为消息指定一个“主题”,表示将消息发布到该主题;而对于订阅消息的客户端来说,可通过订阅“主题”来订阅消息,这样当其它客户端或自己(当前客户端)向该主题发布消息时,MQTT 服务端就会将该主题的信息发送给该主题的订阅者(客户端)。
    值得注意的是, MQTT 客户端在通信时,角色往往不是单一的,一个客户端既可以作为信息发布者也可以同时作为信息订阅者。如下图所示:

    MQTT 发布/订阅特性

    MQTT 通信的核心枢纽是 MQTT 服务端,它负责将 MQTT 客户端发送来的 信息传递给 MQTT 客户端,还负责管理 MQTT 客户端,以确保客户端之间的通讯顺畅,保证 MQTT 信息得以正确接收和准确投递。
    客户端相互独立: MQTT 客户端是一个个独立的个体,它们无需了解彼此的存在,依然可以实现 信息交流。
    时间上可异步: MQTT 客户端在发送和接收信息时无需同步。这一特点对物联网设备尤为重要, 前面我们也介绍了,MQTT 从诞生之初就是专为低带宽、高延迟或不可靠的网络而设计的,高延 迟和不可靠网络必然就会导致时间上的异步;物联网设备在运行过程中发生意外掉线是非常正常 的情况,我们使用上面的实例二的场景来作说明,当开发板在运行过程中,可能会由于突然断电 (假设开发板是通过电源适配器供电的)导致掉线,这时开发板会断开与 MQTT 服务端的连接。 假设此时我们的手机客户端向开发板客户端所订阅的“LED 控制”主题发布了信息,而开发板恰 恰不在线,这时,MQTT 服务端可以将“ LED 控制”主题的新信息保存,待开发板客户端再次上线后,服务端再将“LED 控制”信息推送给开发板。所以这就必然导致了,手机发送信息与开发板接收信息在时间上是异步的。

    二、连接MQTT服务端

    MQTT 客户端之间想要实现通信,必须要通过 MQTT 服务端。所以,客户端无论是发布信息还是订阅信息都必须先连接到服务端。下面我们来看一下,客户端连接服务端的详细过程。

    MQTT 客户端连接服务端总共包含了两个步骤:

    ①、首先客户端需要向服务端发送连接请求,这个连接请求实际上就是向服务端发送一个 CONNECT报文,也就是发送了一个 CONNECT 数据包。
    ②、 MQTT 服务端收到连接请求后,会向客户端发送连接确认。连接确认实际上是向客户端发送一个CONNACK 报文,也就是 CONNACK 数据包。

    CONNECT 报文

    在上面的描述中我们看到, MQTT 客户端要想连接服务端,首先要向服务端发送 CONNECT 报文。如 果此 CONNECT 报文的格式或内容不符合 MQTT 规范,则服务器会拒绝客户端的连接请求。
    一个CONNECK报文内容举例如下:
                     
    所谓报文就是一个数据包, MQTT 报文组成分为三个部分:固定头( Fixed header )、可变头( Variable header)以及有效载荷( Payload ,消息体)。这里我们简单地介绍一下:
    固定头( Fixed header ): 存在于所有 MQTT 报文中,固定头中有报文类型标识,可用于识别是哪 种 MQTT 报文,譬如该报文是 CONNECT 报文还是 CONNACK 报文,亦或是其它类型报文。
    可变头( Variable header ): 存在于部分类型的 MQTT 报文中,报文的类型决定了可变头是否存 在及其具体的内容。
    消息体( Payload ): 存在于部分类型的 MQTT 报文中, payload 就是消息载体的意思。
    在上图中的报文内容有:
    clientId-- 客户端 id
    clientId MQTT 客户端的标识,也就是 MQTT 客户端的名字, MQTT 服务端可通过 clientId 来区分不同的客户端,MQTT 服务端用该标识来识别客户端。
    keepAlive-- 心跳时间间隔
    keepAlive 其实是指定了心跳时间间隔,也就是客户端向服务端发送心跳包的时间间隔。譬如 keepAlive=60 ,表示告诉服务端,客户端将会每隔 60 秒左右向服务端发送心跳包。
    cleanSession-- 清除会话
    cleanSession 设置为 1 ,表示此次连接将创建一个新的临时会话,在客户端断开后,这个会话会自动销毁。而 cleanSession 设置为 0 ,表示创建一个持久性会话,在客户端断开连接时,会话仍然保持并保存离线消息,直到会话超时注销。

    CONNACK 报文

                      

    returnCode-- 连接返回码
    当服务端收到了客户端的连接请求后,会向客户端发送 returnCode( 连接返回码 ) ,用来说明连接情况。 如果客户端与服务端成功连接,则返回数字“0 ”。如果未能成功连接,返回码将会是一个非零的数字。
    sessionPresent
    CONNACK 报文的 sessionPresent CONNECT 报文的 cleanSession 相互配合。其作用是客户端发送连接请求时,服务端告知客户端有没有保存会话状态。这个被服务端保存的会话状态是来自于上一 次客户端连接时,譬如离线消息以及上一次连接时客户端所订阅的主题。
    若基于TCP协议则连接建立过程如下:
    1. 客户端 服务器
    2. | |
    3. |---- TCP SYN ------------> |
    4. | |
    5. |<--- TCP SYN-ACK --------- |
    6. | |
    7. |---- TCP ACK ------------> |
    8. | (TCP 连接建立) |
    9. | |
    10. |---- MQTT CONNECT -------> |
    11. | |
    12. |<--- MQTT CONNACK -------- |
    13. | (MQTT 连接建立) |

    三、断开连接

    MQTT 客户端连接到服务端之后,在后续的通信过程中,如果客户端想要断开与服务端的连接,此 时客户端可以主动向服务端发送一个 DISCONNECT 报文来断开与服务端的连接,如下图所示:
             

    四、发布消息、订阅主题与取消订阅主题

    当客户端连接到服务端之后,便可以发布消息或订阅主题了。

    PUBLISH–发布消息

    当客户端连接到服务端之后,就可以向服务端发布消息了,每条发布的消息必须指定一个“主题”,表示向某主题发布消息;MQTT 服务端可以通过主题来确定将消息转发给哪些客户端(订阅了该主题的客户端)。
    MQTT 客户端向服务端发布消息其实就是向服务端发送一个 PUBLISH 报文,服务端收到客户端发送过来的 PUBLISH 报文之后,会向发送方回复一个报文。根据 QoS 的不同,回复的报文类型也是不同的,并且 整个发布消息的过程也将会有所区别;譬如对于 QoS=1 时,客户端向服务端发送 PUBLISH 报文,服务端 收到 PUBLISH 报文之后会向发送方回复 PUBACK 报文;而对于 QoS=2 的情况,将会更加复杂。
    下图是 PUBLISH 报文包含的信息:
                   
    packetId-- 报文标识符
    报文标识符可用于对 MQTT 报文进行标识(识别不同的报文)。不同的 MQTT 报文所拥有的标识符不同。MQTT 设备可以通过该标识符对 MQTT 报文进行甄别和管理, MQTT 协议内部使用的标识符。请注意:
    报文标识符的内容与 QoS 级别有密不可分的关系。只有 QoS 级别大于 0 时,报文标识符才是非零数值。如果 QoS 等于 0 ,报文标识符为 0
    topicName-- 主题名字
    这个就是发布消息时对应的主题的名字,这是一个字符串,譬如上图中 topicName= myTopic ”,表示会将消息发布到“myTopic ”这个主题。
    payload-- 有效载荷
    有效载荷是我们希望通过 MQTT 所发送的实际内容。我们可以使用 MQTT 协议发送字符串文本,图像等格式的内容。这些内容都是通过有效载荷所发送的。
    qos-- 服务质量等级
    QoS Quality of Service )表示 MQTT 消息的服务质量等级。 QoS 有三个级别: 0 1 2 QoS 决定 MQTT 通信有什么样的服务保证。
    retain-- 保留标志
    在默认情况下,当客户端订阅了某一主题后,并不会马上接收到该主题的信息。因为客户端订阅该主题 之后,并没有其它客户端向该主题发布消息;只有在客户端订阅该主题后,服务端接收到该主题的新消息时,服务端才会将最新接收到的该主题消息推送给客户端。
    但是在有些情况下,我们需要客户端在订阅了某一主题后马上接收到一条该主题的信息。这时候就需要用到保留标志这一信息。
    dup-- 重发标志
    dup 标志指示此消息是否重复。
    MQTT 报文的接收方没有及时向报文发送发回复 确认收到报文 时,发送方会以为对方没有收到信息,会再次重复发送 MQTT 报文(譬如客户端向服务端发送 PUBLISH 报文,服务端收到 PUBLISH 报文之后需要向客户端回复一个 PUBACK 报文,如果客户端没收到 PUBACK 报文,则会认为服务端可能没接收到自己发送的报文,将会再次发送 PUBLISH 报文)。在重复发送 MQTT 报文时,发送方会将此“dup--重发标志”设置为 true 请注意,重发标志只在 QoS 级别大于 0 时使用。

    SUBSCRIBE--订阅主题

    客户端要想接收消息,首先要订阅该消息的主题。这样,当有客户端向该主题发布消息后,订阅了该主题的客户端就能接收到消息了。
    客户端要想订阅主题,首先要向服务端发送主题订阅请求。客户端是通过向服务端发送 SUBSCRIBE 报文来实现这一请求的。该报文包含有一系列“订阅主题名”。请留意,一个 SUBSCRIBE 报文可以包含有单个或者多个订阅主题名。也就是说,一个 SUBSCRIBE 报文可以用于订阅一个或者多个主题。服务端会根据 SUBSCRIBE 中的 QoS 来提供相应的服务保证。
      
    SUBACK 报文包含有“订阅返回码”和“报文标识符”这两个信息。
    returnCode-- 订阅返回码
    客户端向服务端发送订阅请求后,服务端会给客户端返回一个订阅返回码。在之前的讲解中我们说过,客户端可通过一个 SUBSCRIBE 报文发送多个主题的订阅请求。服务端会针对 SUBSCRIBE 报文中的所有订阅主题来逐一回复给客户端一个返回码。这个返回码的作用是告知客户端是否成功订阅了主题。
               

    UNSUBSCRIBE--取消订阅主题

    客户端订阅了某一主题之后,可以随时取消订阅, MQTT 协议提供了这样的操作。
    客户端通过向服务端发送一个 UNSUBSCRIBE 报文来取消订阅主题,当服务端接收到 UNSUBSCRIBE报文后,会向发送发回复一个 UNSUBACK 报文(取消订阅确认报文),如下图所示:

    五、主题的进阶

    主题的基本形式就是一个字符串,譬如: "myTopic" "currentTemp" "LEDControl"等,但是有几个点需要大家注意一下:

    1、主题形式

    主题是区分大小写的。 所以 "LEDControl" "ledControl" 是两个不同的主题。
    主题可以使用空格。 譬如 "LED Control" ,虽然主题允许使用空格,但是笔者建议大家尽量不要使用空格。
    不要使用中文主题。 虽然有些 MQTT 服务器支持中文主题,但是绝大部分 MQTT 服务器是不支持中文主题的,所以大家不要使用中文主题,而是使用 ASCII 字符来作为 MQTT 主题。

    2、主题分级

    MQTT 主题可以是一个简单的字符串,譬如: "myTopic" "currentTemp" "LEDControl" ,事实上, MQTT协议为了更好的对主题进行管理和分类,支持主题分级,对主题进行分级处理,各个级别之间使用" / " 符号进行分隔。如下所示:
    "home/sensor/led/brightness"
    在以上示例中一共有四级主题,分别是第 1 home 、第 2 sensor 、第三级 led 、第 4 brightness 。 主题的每一级至少需要一个字符;而只有一个简单字符串的主题,如"myTopic" "currentTemp"、 "LEDControl",这些都是单一级别的主题。需要注意的是,主题名称不要使用 " / " 开头。

    3、主题通配符

    当客户端订阅主题时,可以使用通配符同时订阅多个主题。通配符只能在订阅主题时使用,分为单级通配符和多级通配符。

    4、主题应用注意事项

    $ 开头的主题
    $ 号开头的主题是 MQTT 服务端系统保留的特殊主题,客户端不可随意订阅或向其发布信息
    不要使用“ / ”作为主题开头
    尽量不要使用“ / ”作为主题的开头,这样做没有什么意义,而且额外产生一个没有用处的主题级别。
    主题中不要使用空格
    虽然, MQTT 支持在主题中使用空格,但是我们应该尽量避免使用空格。
    保持主题简洁明了
    MQTT 是一种轻量级的通讯协议,它常用于网络带宽受限的环境,因此我们应尽量让主题简洁明了,从而让设备间交互的内容更加简洁,以更好的适应网络带宽受限的环境。
    主题中尽量使用 ASCII 字符
    虽然有些 MQTT 设备支持 UTF-8 字符作为 MQTT 主题,建议在主题中尽量使用 ASCII 字符。
  • 相关阅读:
    css / scss 样式变量
    springSecurity认证功能初体验
    PyTorch常用5个抽样函数
    我的十年编程路 序
    线程池:ThreadPoolExecutor源码解读
    推荐一款采用 .NET 编写的 反编译到源码工具 Reko
    第一次实操Python+robotframework接口自动化测试
    vs工具箱在哪里找
    Windows手动清理C盘
    【TypeScript】什么是字面量类型、类型推断、类型拓宽和类型缩小?
  • 原文地址:https://blog.csdn.net/qq_47258284/article/details/139701040