Files
TencentOS-tiny/components/connectivity/mqttclient/docs/mqtt-introduction.md
2020-06-18 19:49:31 +08:00

197 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MQTT协议简介
MQTT协议全称是Message Queuing Telemetry Transport翻译过来就是消息队列遥测传输协议它是物联网常用的应用层协议运行在TCP/IP中的应用层中依赖TCP协议因此它具有非常高的可靠性同时它是基于TCP协议的 <客户端-服务器> 模型发布/订阅主题消息的轻量级协议也是我们常说的发送与接收数据下面我们来初步了解一下mqtt相关的名称与功能。
MQTT最大的优点在于可以以极少的代码和有限的带宽为远程设备提供实时可靠的消息服务。做为一种低开销、低带宽占用的即时通讯协议MQTT在物联网、小型设备、移动应用等方面有广泛的应用。
# MQTT是哪一层的协议
众所周知TCP/IP参考模型可以分为四层应用层、传输层、网络层、链路层。TCP和UDP位于传输层应用层常见的协议有HTTP、FTP、SSH等。MQTT协议运行于TCP之上属于应用层协议因此只要是支持TCP/IP协议栈的地方都可以使用MQTT。
# MQTT通信模型
MQTT 协议提供一对多的消息发布,可以降低应用程序的耦合性,用户只需要编写极少量的应用代码就能完成一对多的消息发布与订阅,该协议是基于<客户端-服务器>模型在协议中主要有三种身份发布者Publisher、服务器Broker以及订阅者Subscriber。其中MQTT消息的发布者和订阅者都是客户端服务器只是作为一个中转的存在将发布者发布的消息进行转发给所有订阅该主题的订阅者发布者可以发布在其权限之内的所有主题并且消息发布者可以同时是订阅者实现了生产者与消费者的脱耦发布的消息可以同时被多个订阅者订阅。
MQTT通信模型示意图如下
![mqtt001](http://qiniu.jiejie01.top/mqtt001.png)
## MQTT客户端的功能
1. 发布消息给其它相关的客户端。
2. 订阅主题请求接收相关的应用消息。
3. 取消订阅主题请求移除接收应用消息。
4. 从服务端终止连接。
## MQTT客户服务器功能
MQTT 服务器常被称为 Broker消息代理以是一个应用程序或一台设备它一般为云服务器比如BTA三巨头的一些物联网平台就是常使用MQTT协议它是位于消息发布者和订阅者之间以便用于接收消息并发送到订阅者之中它的功能有
1. 接受来自客户端的网络连接请求。
2. 接受客户端发布的应用消息。
3. 处理客户端的订阅和取消订阅请求。
4. 转发应用消息给符合条件的已订阅客户端(包括发布者自身)。
# 消息主题与服务质量
什么是主题MQTT服务器为每个连接的客户端订阅者添加一个标签该标签与服务器中的所有订阅相匹配服务器会将消息转发给与标签相匹配的每个客户端订阅者当然订阅者也是需要有权限才能订阅对应的主题比如像阿里云中的订阅者只能订阅同一个产品下的主题而不能跨产品订阅这样子的处理就能达到信息的安全性以及多个订阅者能及时收到消息。一个主题可以有多个级别各个级别之间用斜杠字符分隔例如/test 和 /test/test1/test2都 是有效的主题。
发布者与订阅者可以通过主题名字一般为UTF-8编码反正用英文字符串就不会错的形式发布和订阅主题比如我们可以直接定义一个名字为“test”的主题绝大多数的MQTT服务器支持动态发布/定阅主题,即当前服务器中没有某个主题,但是客户端直接可以向该主题发布/订阅消息,这样子服务器就会创建对应的主题,当然,服务器中一般也会默认提供多个系统主题,所有连接的客户端均可订阅。
每个客户端与服务器建立连接后就是一个会话客户端和服务器之间会有状态交互订阅是基于会话之上每个订阅中都会包含一个主题过滤器它是一个表达式用于标识订阅相关的一个或多个主题主题过滤器可以使用通配符因此订阅者需要指定订阅的主题名字与服务质量QoS订阅者能订阅多个主题也就能接收到多个发布者发布的消息。同理发布者也需要首先与服务器建立会话并且指定发送的主题名字与服务质量同时它也能向多个不同的主题发送消息。
那么什么是服务质量呢MQTT的服务质量提供3个等级
1. QoS0最多发送一次消息在消息发送出去后接收者不会发送回应发送者也不会重发消息消息可能送达一次也可能根本没送达这个服务质量常用在不重要的消息传递中因为即使消息丢了也没有太大关系。
2. QoS1最少发送一次消息消息最少需要送达一次也有可送达多次QoS 1的PUBLISH报文的可变报头中包含一个报文标识符需要PUBACK报文确认。即需要接收者返回PUBACK应答报文。
3. QoS2这是最高等级的服务质量消息丢失和重复都是不可接受的只不过使用这个服务质量等级会有额外的开销这个等级常用于支付中因为支付是必须有且仅有一次成功总不能没给钱或者给了多次钱吧。
# MQTT控制报文
## 固定报头
MQTT协议工作在TCP协议之上因为客户端和服务器都是应用层那么必然需要一种协议在两者之间进行通信那么随之而来的就是MQTT控制报文 MQTT控制报文有3个部分组成分别是固定报头fixed header、可变报头variable header、有效荷载数据区域payload。固定报头所有的MQTT控制报文都包含可变报头与有效载荷是部分MQTT控制报文包含。
固定报头占据两字节的空间,具体见
![mqtt002](http://qiniu.jiejie01.top/mqtt002.png)
固定报头的第一个字节分为控制报文的类型4bit以及控制报文类型的标志位控制类型共有14种其中0与15被系统保留出来其他的类型具体见
| 类型 | 值 | 说明 |
| -- | -- | -- |
| Reserved | 0 | 系统保留 |
| CONNECT | 1 | 客户端请求连接服务端 |
| CONNACK | 2 | 连接报文确认 |
| PUBLISH | 3 | 发布消息 |
| PUBACK | 4 | 消息发布收到确认QoS 1 |
| PUBREC | 5 | 发布收到QoS2 |
| PUBREL | 6 | 发布释放QoS2 |
| PUBCOMP | 7 | 消息发布完成QoS2 |
| SUBSCRIBE | 8 | 客户端订阅请求 |
| SUBACK | 9 | 订阅请求报文确认 |
| UNSUBSCRIBE | 10 | 客户端取消订阅请求 |
| UNSUBACK | 11 | 取消订阅报文确认 |
| PINGREQ | 12 | 心跳请求 |
| PINGRESP | 13 | 心跳响应 |
| DISCONNECT | 14 | 客户端断开连接 |
| Reserved | 15 | 系统保留 |
固定报头的bit0-bit3为标志位依照报文类型有不同的含义事实上除了PUBLISH类型报文以外其他报文的标志位均为系统保留PUBLISH报文的第一字节bit3是控制报文的重复分发标志DUPbit1-bit2是服务质量等级bit0是PUBLISH报文的保留标志用于标识PUBLISH是否保留当客户端发送一个PUBLISH消息到服务器如果保留标识位置1那么服务器应该保留这条消息当一个新的订阅者订阅这个主题的时候最后保留的主题消息应被发送到新订阅的用户。
固定报头的第二个字节开始是剩余长度字段,是用于记录剩余报文长度的,表示当前的消息剩余的字节数,包括可变报头和有效载荷区域(如果存在),但剩余长度不包括用于编码剩余长度字段本身的字节数。
剩余长度字段使用一个变长度编码方案对小于128的值它使用单字节编码而对于更大的数值则按下面的方式处理每个字节的低7位用于编码数据长度最高位bit7用于标识剩余长度字段是否有更多的字节且按照大端模式进行编码因此每个字节可以编码128个数值和一个延续位剩余长度字段最大可拥有4个字节。
- 当剩余长度使用1个字节存储时其取值范围为0(0x00)~127(0x7f)。
- 当使用2个字节时其取值范围为128(0x80,0x01)~16383(0Xff,0x7f)。
- 当使用3个字节时其取值范围为16384(0x80,0x80,0x01)~2097151(0xFF,0xFF,0x7F)。
- 当使用4个字节时其取值范围为2097152(0x80,0x80,0x80,0x01)~268435455(0xFF,0xFF,0xFF,0x7F)。
总的来说MQTT报文理论上可以发送最大256M的报文当然这种情况是非常少的。
## 可变报头
可变报头并不是所有的MQTT报文都带有的比如PINGREQ心跳请求与PINGRESP心跳响应报文就没有可变报头只有某些报文才拥有可变报头它在固定报头和有效负载之间可变报头的内容会根据报文类型的不同而有所不同但可变报头的报文标识符Packet Identifier字段存在于在多个类型的报文里而有一些报文又没有报文标识符字段具体见表格报文标识符结构具体见图。
| 报文类型 | 是否需要报文标识符字段 |
| -- | -- |
| CONNECT | 不需要 |
| CONNACK | 不需要 |
| PUBLISH | 需要如果QoS > 0 |
| PUBACK | 需要 |
| PUBREC | 需要 |
| PUBREL | 需要 |
| PUBCOMP | 需要 |
| SUBSCRIBE | 需要 |
| SUBACK | 需要 |
| UNSUBSCRIBE | 需要 |
| UNSUBACK | 需要 |
| PINGREQ | 不需要 |
| PINGRESP | 不需要 |
| DISCONNECT | 不需要 |
![mqtt003](http://qiniu.jiejie01.top/mqtt003.png)
因为对于不同的报文,可变报头是不一样的,下面就简单讲解几个报文的可变报头。
## CONNECT报文
在一个会话中客户端只能发送一次CONNECT报文它是客户端用于请求连接服务器的报文常称之为连接报文如果客户端发送多次连接报文那么服务端必须将客户端发送的第二个CONNECT报文当作协议违规处理并断开客户端的连接。
CONNECT报文的可变报头包含四个字段协议名Protocol Name、协议级别Protocol Level、连接标志Connect Flags以及保持连接Keep Alive字段。
协议名是MQTT 的UTF-8编码的字符串其中还包含用于记录协议名长度的两字节字段MSB与LSB。
在协议名之后的是协议级别MQTT协议使用8位的无符号值表示协议的修订版本对于MQTT3.1版的协议协议级别字段的值是3(0x03)而对于MQTT3.1.1版的协议协议级别字段的值是4(0x04)。如果服务器发现连接报文中的协议级别字段是不支持的协议级别服务端必须给发送一个返回码为0x01不支持的协议级别的CONNACK响应连接报文然后终止客户端的连接请求。
连接标志字段涉及的内容比较多,它在协议级别之后使用一个字节表示,但分成很多个标志位,具体见
![mqtt004](http://qiniu.jiejie01.top/mqtt004.png)
bit0是MQTT保留的标志位在连接过程中服务器会检测连接标志的bit0是否为0如果不为0则服务器任务这个连接报文是不合法的会终止连接请求。
bit1是清除会话标志Clean Session一般来说客户端在请求连接服务器时总是将清除会话标志设置为0或1在建立会话连接后这个值就固定了当然这个值的选择取决于具体的应用如果清除会话标志设置为1那么客户端不会收到旧的应用消息而且在每次连接成功后都需要重新订阅相关的主题。清除会话标志设置为0的客户端在重新连接后会收到所有在它连接断开期间其他发布者发布的QoS1和QoS2级别的消息。因此要确保不丢失连接断开期间的消息需要使用QoS1或 QoS2级别同时将清除会话标志设置为0。
bit2是遗嘱标志 Will Flag如果该位被设置为1表示如果客户端与服务器建立了会话遗嘱消息Will Message将必须被存储在服务器中当这个客户端断开连接的时候遗嘱消息将被发送到订阅这个会话主题的所有订阅者这个消息是很有用的我们可以知道这个设备的状况它是否已经掉线了以备启动备用方案当然想要不发送遗嘱消息也是可以的只需要让服务器端收到DISCONNECT报文时删除这个遗嘱消息即可。
bit3-bit4用于指定发布遗嘱消息时使用的服务质量等级与其他消息的服务质量是一样的遗嘱QoS的值可以等于0(0x00)1(0x01)2(0x02)当然使用遗嘱消息的前提是遗嘱标志位为1。
bit5表示遗嘱保留标志位当客户端意外断开连接时如果 Will Retain置一那么服务器必须将遗嘱消息当作保留消息发布反之则无需保留。
bit6是密码标志位Password Flag如果密码标志被设置为0有效载荷中不能包含密码字段反之则必须包含密码字段。
bit7是用户名标志位User Name Flag如果用户名标志被设置为0有效载荷中不能包含用户名字段反之则必须包含用户名字段。
保持连接字段是一个以秒为单位的时间间隔它使用了两个字节来记录允许客户端最大空闲时间间隔简单来说就是客户端必须在这段时间中与服务器进行通信让服务器知道客户端还处于连接状态而不是断开了当然如果没有任何其它的控制报文可以发送客户端也必须要发送一个PINGREQ报文以告知服务器还是处于连接状态的。
总的来说整个CONNECT报文可变报头的内容如下具体见
![mqtt005](http://qiniu.jiejie01.top/mqtt005.png)
## CONNACK报文
我们再来讲解一下CONNACK报文的可变报头部分其实有了上一个的经验这部分对大家来说是很简单的它是由连接确认标志字段Connect Acknowledge Flags与连接返回码字段 Connect Return code组成各占用1个字节。
它的第1个字节是 连接确认标志字段bit1-bit7是保留位且必须设置为0 bit0是当前会话Session Present标志位。
它的第2个字节是返回码字段如果服务器收到一个CONNECT报文但出于某些原因无法处理它服务器会返回一个包含返回码的CONNACK报文。如果服务器返回了一个返回码字段是非0的CONNACK报文那么它必须关闭网络连接返回码描述具体见
| 返回码值 | 描述 |
| -- | -- |
| 0x00 | 连接已被服务端接受 |
| 0x01 | 连接已拒绝服务端不支持客户端请求的MQTT协议级别 |
| 0x02 | 连接已拒绝服务器标识符是正确的UTF-8编码但不允许使用 |
| 0x03 | 连接已拒绝网络连接已建立但MQTT服务不可用 |
| 0x04 | 连接已拒绝,用户名或密码的数据格式无效 |
| 0x05 | 连接已拒绝,客户端未被授权连接到此服务器 |
| 0x06~0xFF | 保留未使用 |
提示如果服务端收到清理会话CleanSession标志为1的连接除了将CONNACK报文中的返回码设置为0之外还必须将CONNACK报文中的当前会话设置Session Present标志为0。
那么总的来说CONNACK报文的可变报头部分内容具体见
![mqtt006](http://qiniu.jiejie01.top/mqtt006.png)
在此就不再对MQTT报文的可变报头部分过多赘述大家可以参考MQTT协议手册里面有很详细的描述。
## 有效载荷
有效载荷也是存在与某些报文中,不同的报文有效载荷也是不一样的,比如:
CONNECT报文的有效载荷payload包含一个或多个以长度为前缀的字段可变报头中的标志决定是否包含这些字段。如果包含的话必须按这个顺序出现客户端标识符遗嘱主题遗嘱消息用户名密码 。
SUBSCRIBE报文的有效载荷包含了一个主题过滤器列表它们标识着客户端想要订阅的主题每一个过滤器后面跟着一个字节这个字节被叫做服务质量要求Requested QoS它给出了服务端向客户端发送应用消息所允许的最大QoS等级。
这里只是讲述了一小部分内容关于具体的有效载荷部分也可以去看MQTT手册此处就不再赘述。
**下一篇**[MQTT通信过程](./mqtt-communication.md)