基本介绍
熟悉物联网的人对MQTT一定不陌生,MQTT可能是目前物联网通信中应用最广泛的标准协议之一,也是很多物联网平台最主流的通信协议,例如阿里云的物联网平台很多就是通过MQTT接入的。
MQTT(Message Queuing Telemetry Transport,中文翻译为消息队列遥测传输),MQTT的第一个版本是由IBM公司的Andy Stanford-Clark及Cirrus Link公司的Arlen Nipper于1999年撰写的。目前MQTT是ISO 标准(ISO/IEC PRF 20922)下基于发布/订阅范式的消息协议,工作在 TCP/IP协议族上。目前MQTT已经成为二OASIS组织(结构化信息标准促进组织)下正式批准的通信标准,目前MQTT有两个标准——3.1.1版本和5.0版本。3.1.1发布于2014年,是比较主流的版本;5.0发布于2019年,在3.1.1的基础上做了更多的补充和完善,也完全兼容3.1.1版本,目前还在不断更新中。本文主要讲解的内容是基于3.1.1版本的标准(参考:MQTT Version 3.1.1 (oasis-open.org))。
MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。因此除了物联网,它还被应用在机器与机器(M2M)通信,手机系统或WEB消息推送,智能家居,车联网等很多场合等等。
基本概念及术语
MQTT是一个基于客户端-服务端的消息发布/订阅传输协议,是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议(为此,它需要一个消息中间件 ,很多物联网通信协议都是这样,例如OPC UA)。这一段解释中说明了MQTT协议中两个重要的角色——服务端和客户端和一个重要行为——消息发布/订阅,下面再详细解释一下。

客户端(Clinet)
客户端是指使用MQTT的程序或者设备,客户端是去连接服务端的,也可以向服务端发送消息或者接收消息。客户端向服务端发送消息就成为“发布”信息,客户端如果要从服务端接收消息,就要向服务端“订阅”信息,当订阅的信息类型被发布时,它就会收到服务端发送的消息。具体它可实现的功能包括:
发布其他客户端可能会感兴趣的应用消息
订阅自己感兴趣的应用消息
退订应用消息
从服务器断开连接
服务端(Server)
服务端是订阅和发布应用消息的客户端之间的中间人,是消息传输的枢纽,同时也负责管理客户端。具体服务端可实现的功能包括
接收客户端的网络连接
接收客户端发布的应用消息
处理客户端订阅和退订的请求
转发匹配客户端订阅的应用消息
会话(Session)
有状态的的服务端和客户端之间的交互。有一些会话仅存在于有网络连接的时候,有一些可以跨越多个连续的网络连接。
主题和主题过滤器(Topic Name and Topic Filter)
主题是匹配订阅的应用消息,这些订阅对服务端已经已知了。服务端会对所有有相同订阅客户端都发送主题。主题过滤器是包含在订阅中的一个表达式,为了表示客户端感兴趣的一个或多个主题。主题过滤器可以包含通配符。
MQTT控制包(MQTT Control Packet)
通过网络连接发送的信息包。MQTT包含了14种不同类型的控制数据包,其中一种(发布数据包)用于传达应用程序消息。
订阅(Subscription)
一个订阅包括了一个主题筛选器和一个最大QoS。一个订阅与单个会话关联,不过一个会话可以包含多个订阅。一个会话中的多个订阅都有不同的主题过滤器。
数据表示
位
一个字节中标记7到0。7是最高有效位,0是最低有效位
整数数据类型
16位的整数数据值按大端顺序:高位字节(MSB)位于低位字节(LSB)之前(MSB比LSB早发送)。
UTF-8编码的字符串
后面控制数据包中的文本字段编码是UTF-8字符串。
字符串的每一个都已两个字节为前缀长度字段,是UTF-8编码字符串的本身字节数。因此UTF-8编码字符串中传递的字符数不能超过65535个。除非有另外说明,否则UTF-8编码字符串都可以传递0-65535字节范围内的任意长度。
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
字节 1 | 字符串长度 MSB | |||||||
字节 2 | 字符串长度 LSB | |||||||
字节 3 .... | UTF-8 编码 字符数据(如果长度> 0)。 |
UTF-8编码格式必须正确,不能包括U+D800到U+DFFF之间的编码,不能包括空字符U+0000的编码,如果服务器或客户端收到这些编码,都必须关闭网络连接。还有其他非法的
U+0001~U+001F
U+007F~U+009F
或者其他非字符编码U+FFFF。
UTF-8 编码序列0xEF 0xBB 0xBF总是被解释为U + FEFF (“零宽度无中断” 空格“),在字符串中出现的任何位置时,不得跳过或由数据包接收器剥离。
MQTT控制包格式
MQTT控制包最多包括三个部分:
固定头,所有MQTT控制数据包中都有
变量头,某些MQTT控制数据包中存在
有效载荷,某些MQTT控制数据包中存在
固定头
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
字节 1 | MQTT控制包类型 | 每个控制包类型对应的特定标志 | ||||||
字节2 ... | 剩余长度 |
控制包类型和对应标志
名称 | 值 | 信息方向 | 描述 | 标志 | BIT3 | BIT2 | BIT1 | BIT0 |
Reserved | 0 | 不使用 | Reserved | 不使用 | ||||
CONNECT | 1 | 客户端→服务端 | 客户端请求连接到服务端 | 0 | 0 | 0 | 0 | |
CONNACK | 2 | 服务端→客户端 | 连接响应 | 0 | 0 | 0 | 0 | |
PUBLISH | 3 | 双向 | 发布信息 | MQTT 3.1.1中使用 | DUP | QoS | QoS | RETAIN |
PUBACK | 4 | 双向 | 发布响应 | 0 | 0 | 0 | 0 | |
PUBREC | 5 | 双向 | 发布收到(保证交付第1部分) | 0 | 0 | 0 | 0 | |
PUBREL | 6 | 双向 | 发布发出(保证交付第2部分) | 0 | 0 | 1 | 0 | |
PUBCOMP | 7 | 双向 | 发布完成(保证交付第3部分) | 0 | 0 | 0 | 0 | |
SUBSCRIBE | 8 | 客户端→服务端 | 客户端的订阅请求 | 0 | 0 | 1 | 0 | |
SUBACK | 9 | 服务端→客户端 | 订阅响应 | 0 | 0 | 0 | 0 | |
UNSUBSCRIBE | 10 | 客户端→服务端 | 取消订阅请求 | 0 | 0 | 1 | 0 | |
UNSUBACK | 11 | 服务端→客户端 | 取消订阅响应 | 0 | 0 | 0 | 0 | |
PINGREQ | 12 | 客户端→服务端 | PING 请求 | 0 | 0 | 0 | 0 | |
PINGRESP | 13 | 服务端→客户端 | PING 响应 | 0 | 0 | 0 | 0 | |
DISCONNECT | 14 | 客户端→服务端 | 客户端断开 | 0 | 0 | 0 | 0 | |
Reserved | 15 | 不使用 | Reserved | 不使用 |
*虽然一些数据包类型的标志没有用,但是必须也和上表设置的一样。如果收到无效的标志,接收器需要关闭网络连接。
*DUP:一个PUBLISH控制包重复发送
*QoS:PUBLISH服务质量
*RETAIN:PUBLISH保留标志
下面会有详细说明
剩余长度
从字节2开始,当前数据包中剩余的字节数,包括变量头和有效载荷。剩余长度不包括编码剩余长度本身的字节。
剩余长度的表示方式比较少见。最多占用四个Byte。一个Byte只有低的7位用来表示长度,最高位表示接下去的那个字节还是不是剩余长度的字节。后面的字节是表示高位,前面的字节表示低位,所以最高科表示268,435,455 个字节长度,即256 MB。
Digits | From | To |
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) |
变量头
一些类型的控制包是有变量头的。变量头就是一般包括一个包标志符和其他的一些变量(这里没有说明,参见下一部分)。
数据包标志符
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
byte 1 | 数据包标志符 MSB | |||||||
byte 2 | 数据包标志符 LSB |
以下表格表明了那些控制包需要包含数据表标志符
控制包 | 数据包标志符 |
CONNECT | NO |
CONNACK | NO |
PUBLISH | YES (If QoS > 0) |
PUBACK | YES |
PUBREC | YES |
PUBREL | YES |
PUBCOMP | YES |
SUBSCRIBE | YES |
SUBACK | YES |
UNSUBSCRIBE | YES |
UNSUBACK | YES |
PINGREQ | NO |
PINGRESP | NO |
DISCONNECT | NO |
SUBSCRIBE, UNSUBSCRIBED和PUBLISH的数据包标志符不能为零
一组SUBSCRIBE和其SUBACK,一组UNSUBSCRIBE和UNSUBACK对应的标志符肯定一样。
对于PUBLISH,如果QoS=1,那对应的PUBACK的标志符一样;如果QoS=2,那对应PUBCOMP必须一样;如果QoS = 0,那PUBLISH就不能包含标志符。(PUBACK, PUBREC, PUBREL)都必须包含和原先发布的PUBLISH一样的标志符。
有效载荷
有效载荷在各个控制包类型的包含情况参见下表。
控制包 | 有效载荷 |
CONNECT | Required |
CONNACK | None |
PUBLISH | Optional |
PUBACK | None |
PUBREC | None |
PUBREL | None |
PUBCOMP | None |
SUBSCRIBE | Required |
SUBACK | Required |
UNSUBSCRIBE | Required |
UNSUBACK | None |
PINGREQ | None |
PINGRESP | None |
DISCONNECT | None |