MQTT 的全称为 Message Queue Telemetry Transport(消息队列遥测传输协议),是在 1999 年,由 IBM 的 Andy Stanford-Clark 和 Arcom 的 Arlen Nipper 为了一个通过卫星网络连接输油管道的项目开发的。为了满足低电量消耗和低网络带宽的需求,MQTT 协议在设计之初就包含了以下一些特点:
- 实现简单
- 提供数据传输的 QoS
- 轻量、占用带宽低
- 可传输任意类型的数据
- 可保持的会话(session)
之后 IBM 一直将 MQTT 作为一个内部协议在其产品中使用,直到 2010 年,IBM 公开发布了 MQTT 3.1 版本。在 2014 年,MQTT 协议正式成为了 OASIS(结构化信息标准促进组织)的标准协议。简单地来说MQTT协议具有以下特性:
- 基于 TCP 协议的应用层协议;
- 采用 C/S 架构;
- 使用订阅/发布模式,将消息的发送方和接受方解耦;
- 提供 3 种消息的 QoS(Quality of Service): 至多一次,最少一次,只有一次;
- 收发消息都是异步的,发送方不需要等待接收方应答。
下文将从以下四个方面对MQTT的基础概念进行介绍:
- MQTT 协议的通信模型
- MQTT Client
- MQTT Broker
- MQTT协议数据包
1. MQTT 协议的通信模型
MQTT 的通信是通过发布/订阅的方式来实现的,订阅和发布又是基于主题(Topic)的。发布方和订阅方通过这种方式来进行解耦,它们没有直接地连接,它们需要一个中间方。在 MQTT 里面我们称之为 Broker,用来进行消息的存储和转发。一次典型的 MQTT 消息通信流程如下所示:
- 发布方(Publisher)连接到Broker;
- 订阅方(Subscriber)连接到Broker,并订阅主题Topic1;
- 发布方(Publisher)发送给Broker一条消息,主题为Topic1;
- Broker收到了发布方的消息,发现订阅方(Subscriber)订阅了Topic1,然后将消息转发给订阅方(Subscriber);
- 订阅方从Broker接收该消息;
MQTT通过订阅与发布模型对消息的发布方和订阅方进行解耦后,发布方在发布消息时并不需要订阅方也连接到Broker,只要订阅方之前订阅过相应主题,那么它在连接到Broker之后就可以收到发布方在它离线期间发布的消息。我们可以称这种消息为离线消息。
在该通信模型中,有两组身份需要区别:
- 一组是发布方Publisher和订阅方Subscriber
- 另一组是发送方Sender和接收方Receiver
1.1. Publisher和Subscriber
publisher和subscriber是相对于Topic来说的身份,如果一个Client向某个Topic发布消息,那么这个Client就是publisher;如果一个Client订阅了某个Topic,那么它就是Subscriber。
1.2. Sender和Receiver
Sender和Receiver则是相对于消息传输方向的身份。当publisher向Broker发送消息时,那么此时publisher是sender,Broker是receiver;当Broker转发消息给subscriber时,此时Broker是sender,subscriber是receiver。
2. MQTT Client
Publisher 和 Subscriber 都属于 Client,Pushlisher 或者 Subscriber 只取决于该 Client 当前的状态——是在发布消息还是在订阅消息。当然,一个 Client 可以同时是 Publisher 和 Subscriber。client的范围很广,任何终端、嵌入式设备、服务器只要运行了MQTT的库或者代码,都可以称为MQTT Client。MQTT Client库很多语言都有实现,可以在这个网址中找到:MQTT Client库大全
3. MQTT Broker
MQTT Broker负责接收Publisher的消息,并发送给相应的Subscriber,是整个MQTT 订阅/发布的核心。现在很多云服务器提供商都有提供MQTT 服务,比如阿里云、腾讯云等。当然我们自己也可以搭建一个MQTT Broker,个人倾向于使用 mosquitto来搭建自己的MQTT Broker。mosquitto官方网址
4. MQTT协议数据包
MQTT 协议数据包的消息格式为:固定头|可变头|消息体
由下面三个部分组成:
- 固定头(Fixed header):存在于所有的MQTT数据包中,用于表示数据包类型及对应标志、数据包大小等;
- 可变头(Variable header):存在于部分类型的MQTT数据包中,具体内容是由相应类型的数据包决定的;
- 消息体(Payload):存在于部分的MQTT数据包中,存储消息的具体数据。
4.1. 固定头
固定头格式:
固定头的第一个字节的高4位Bit用于表示该数据包的类型,MQTT的数据包有以下一些类型:
名称 | 值 | 方向 | 描述 |
---|---|---|---|
Reserved | 0 | 不可用 | 保留位 |
CONNECT | 1 | Client 到 Broker | Client 请求连接到 Broker |
CONNACK | 2 | Broker 到 Client | 连接确认 |
PUBLISH | 3 | 双向 | 发布消息 |
PUBACK | 4 | 双向 | 发布确认 |
PUBREC | 5 | 双向 | 发布收到 |
PUBREL | 6 | 双向 | 发布释放 |
PUBCOMP | 7 | 双向 | 发布完成 |
SUBSCRIBE | 8 | Client 到 Broker | Client 请求订阅 |
SUBACK | 9 | Broker 到 Client | 订阅确认 |
UNSUBSCRIBE | 10 | Client 到 Broker | Client 请求取消订阅 |
UNSUBACK | 11 | Broker 到 Client | 取消订阅确认 |
PINGREQ | 12 | Client 到 Broker | PING 请求 |
PINGRESP | 13 | Broker 到 Client | PING 应答 |
DISCONNECT | 14 | Client 到 Broker | Client 主动中断连接 |
Reserved | 15 | 不可用 | 保留位 |
低4位Bit则用于表示数据包的Flag,不同的数据包类型,Flag的定义不同,每种数据包对应的Flag如下:
数据包 | 标识位 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
---|---|---|---|---|---|
CONNECT | 保留位 | 0 | 0 | 0 | 0 |
CONNACK | 保留位 | 0 | 0 | 0 | 0 |
PUBLISH | MQTT 3.1.1 使用 | DUP | QoS | QoS | RETAIN |
PUBACK | 保留位 | 0 | 0 | 0 | 0 |
PUBREC | 保留位 | 0 | 0 | 0 | 0 |
PUBREL | 保留位 | 0 | 0 | 0 | 0 |
PUBCOMP | 保留位 | 0 | 0 | 0 | 0 |
SUBSCRIBE | 保留位 | 0 | 0 | 0 | 0 |
SUBACK | 保留位 | 0 | 0 | 0 | 0 |
UNSUBSCRIBE | 保留位 | 0 | 0 | 0 | 0 |
UNSUBACK | 保留位 | 0 | 0 | 0 | 0 |
PINGREQ | 保留位 | 0 | 0 | 0 | 0 |
PINGRESP | 保留位 | 0 | 0 | 0 | 0 |
DISCONNECT | 保留位 | 0 | 0 | 0 | 0 |
从固定头的第2字节开始是用于表示MQTT数据包剩余长度的字段,**最少一个字节,最大四个字节。每一个字节的低7位用于标识值,范围为0~127。最高的1位是标识位,用来说明是否有后续字节来标识长度。**标识为0,代表没有后续字节;标识为1,代表后续还有一个字节用于标识包的长度。MQTT协议相应字节数对应的最小、最大包长度如下表所示:
字节数 | 最小包长度(除固定头外) | 最大包长度(除固定头外) |
---|---|---|
1 | 0字节(0x00) | 127字节(0x7F) |
2 | 128字节(0x80,0x01) | 16 383字节(0xFF,0x7F) |
3 | 16 384字节(0x80,0x80,0x01) | 2 097 151字节(0xFF,0xFF,0x7F) |
4 | 2 097 152字节(0x80,0x80,0x80,0x01) | 268 435 455字节(0xFF,0xFF,0xFF,0x7F) |
可知MQTT协议中数据包(除固定头外)的最大长度为268435455 字节,约 256M。
注意:上述的剩余长度不包含固定头的大小,是指后面的可变报文头加消息体的总长度
4.2. 可变头
可变报文头主要包含协议名、协议版本、连接标志(Connect Flags)、心跳间隔时间(Keep Alive timer)、连接返回码(Connect Return Code)、主题名(Topic Name)等,后面会针对此部分进行具体讲解。
4.3. 消息体
当MQTT发送的消息类型是CONNECT(连接)、PUBLISH(发布)、SUBSCRIBE(订阅)、SUBACK(订阅确认)、UNSUBSCRIBE(取消订阅)时,则会带有负荷。
5. 发布订阅实验演示
本实验中演示使用的Broker是mosquitto,mosquitto具体的安装步骤不再阐述,可自行搜索安装教程。下面按照[MQTT 协议的通信模型](#MQTT 协议的通信模型)中所提到的流程对发布和订阅这种模式进行一个直观的演示:
- 第一步:启动Broker
- 第二步:subscriber订阅相应的Topic
如图所示,通过mosquitto_sub
命令向Broker订阅了名为test
的Topic,运行Broker的终端界面也打印了相关的 订阅信息。
- 第三步:publisher发布相应Topic的消息
如图所示,通过mosquitto_pub命令向主题名为test
的发布了两条消息,这时候订阅方(即运行mosquitto_sub
命令的终端页面)收到了发布的消息。Broker输出的信息为处理的过程:
- publisher连接到Broker
- Broker回复了一个CONNACK数据包给publisher
- Broker从publisher那收到了PUBLISH数据包
- Broker将其PUBLISH数据包转发给subscriber
- Broker收到了publisher发来的DISCONNECT数据包,然后断开了连接。