目录
目录
1 MQTT概述
1.1 简介
1.2 协议特点
1.3 数据格式与约定
2 控制报文格式
2.1 控制报文总体结构
2.1 固定报头
2.1.1 控制报文类型
2.1.2 标志位
2.1.3 剩余长度
2.2 可变报头
2.3 有效载荷
3 各种控制报文
3.1 CONNECT 连接报文
3.1.1 固定报头
3.1.2 可变报头
3.1.3 有效载荷
3.1.4 服务器的响应
3.2 CONNACK 确认连接请求
3.2.1 固定报头
3.2.2 可变报头
3.2.3 有效载荷
3.3 PUBLISH 发布消息控制报文
3.3.1 固定报头
MQTT(Message Queuing Telemetry Transport)是消息队列遥测传输协议。在五层网络模型中处于应用层,是轻量级基于代理的发布/订阅的消息传输协议。设计特点:简单、轻量、易于实现。 应用场景:特别适合 带宽小、不可靠、硬件资源有限的场景下。可以说是一款物联网通信协议,运行在TCP/IP,或其他有序、可靠、双向连接的网络连接上。
Sender(发送消息者,简称S),Receiver(接收消息者,简称R),Message(消息,简称M),Topic(主题,简称T),Proxy(代理服务器,简称P)。
1. 发布/订阅模式,使得应用之间解耦。首先接收者要去订阅主题,所有的消息都是按照主题来分类的,发送者也需要标识消息的主题。比如,R订阅了主题T,S指明消息M的主题为T并发送到代理服务器P上,P收到消息后,将M按主题T归类,然后将M推送给订阅了此主题T的用户(其中就有R),这样就完成了消息的传输。 为什么说使得应用之间解耦呢 ?试想,发送者和接受者实际上是应用程序,如果没有代理,S和R直接通信,两者程序之间多少都会存在耦合,因为要直接通信对接;如果中间有了代理,那么两者就只是和代理进行通信,之间没有直接有什么对接,所有解除了耦合。
2. 消息传输不需要知道负载内容。 暂时不理解。
3. 提供三种等级的服务质量。“最多一次”:同一个消息,发送者最多发送一次(发送失败或者发送成功),一个消息发送失败后是不会重新发送的,所以此服务等级存在数据丢失问题,可应用于对数据丢失不敏感的场景,比如部分无线传感网络,传感器丢失小部分数据,并不会产生影响。 “至少一次”:同一个消息,发送失败的时候,就会重传,确保该消息达到,所以称为至少一次;但是可能会出现消息重复,为什么可能出现重复消息呢 ?试想,要确保消息到达,那么消息达到后,必定要返回一个信号给发送者,告诉他消息送到了,但是如果由于网络的拥塞等原因,发送者迟迟没有收到返回信号,这个时间超过等待周期,那么发送者就认为消息没有送到,于是重新发送,等发送了之后,返回信号终于到了,但是消息已经被重新发送了,就造成了同一个消息被传输了两次,数据重复了。这个等级应用场景为:对数据的重复不敏感,只要保证每个数据传输成功就行。“只一次”:确保消息到达,且仅到达一次,这个容易理解。应用场景:对数据丢失和数据重复零容忍的场景。(“只一次”等级应该是最好的,直接用它不就行了,为什么还要提供另外两个等级呢?本人认为:提供的服务越完美,那么通信,计算,时间开销越大,服务越不完美,可能通信简单快速,计算简单快速,实时性高,鱼和熊掌不能兼得)。
4. 很小的传输消耗和协议数据交换,最大限度减少网络流量。这是比较明显的一个优势,协议本身简单轻量,所以传输数据量比较小。
5. 异常连接断开发生时,能通知到相关各方。网络连接因为异常而断开时,mqtt协议有机制去通知相关各方。
字节中,第0位到第7位,0位是最低为,7位是最高位。
整数是16位,又分为高8位和低8位,采用大端模式。
文本字符串采用 utf-8 编码格式。每个字符串的前面都有两个字节长度的字段,该字段是指明后面字符串的字节数,因为每个字符串的大小不能超过65535字节。
特别需要注意,字符串是Unicode编码的,U+ 代表是Unicode编码,16进制的。发送者生成的字符串是Unicode编码的,通过MQTT传输之前,要将Unicode编码的字符串转换为 utf-8 编码,传输出去,接收者接收到后,再讲 utf-8 编码格式的字符串转换为 Unicode编码格式。
在MQTT协议中,明确规定哪些Unicode字符码是不能包含的。
[U+D800, U+DFFF] 区间的字符码是无效的,如果接收者接收到了此区间的数据,必须关闭网络连接。
U+0000 空字符,如果接收者接收到了此数据,必须关闭网络连接。
[U+0001, U+001F] [U+007F, U+009F] 区间的字符码,如果接收者接收到了,可以选择关闭网络连接。
非字符字符码,保留字符 ,如果接收者接收到了,可以选择关闭网络连接。
固定报头是固定的,每个控制报文都包含。可变报头,部分控制报文包含。 有效载荷,部分控制报文包含。
标志位是和控制报文类型关联的,类型是保留的,当然就不需要标志位了。Reserved表示此标志位为保留,以后开发扩展用的,重点就是PUBLISH类型的标志位。
DUP1:控制报文的重复分发标志。
QoS2:控制报文的服务质量等级。
RETAIN3:控制报文的保留标志。
剩余长度是剩下报文的字节数,不包含剩余长度本身。
剩余长度最少一个字节,最多四个字节,根据剩余报文大小决定。剩余长度的每个字节分为两部分:第7位 和 0~6位,第7位(最高位)表示是否后面还有字节。
可变报头在每种类型的控制报文里面的字段和含义是不一样的,此处暂时不作介绍,第3节介绍各种报文的时候
有效载荷,可以将其理解为报文所带的附件内容,并不是所有的控制报文都需要有效载荷,对于不同类型的控制报文,有效载荷的意义是不一样的。
在客户端和服务器之间,只要网络是可达的,那么要想客户端和服务器建立连接,客户端就要发送第一个报文CONNECT连接报文,而且只能发送一次CONNECT报文,因为如果服务器收到客户端两个CONNECT报文的话,就必须要以异常处理,断开与客户端的连接。
可变报头包含四个内容,分别是“协议名”、“协议级别”、“连接标志”、“保持连接”。
协议名:协议名就是MQTT,长度为4字节,前两个字节用于存储长度,所以是04,后面4个字节用于存储协议的名字,就是MQTT,每个字母占据一个字节,用 utf-8 编码表示。这块内容基本不会变了,因为MQTT后续版本的协议对此都不会改变。
如果协议名不是MQTT的话,服务器可以断开与客户端的连接。
协议级别:级别和协议的版本是一一对应的,比如本文讲的协议版本是3.1.1 ,那么级别就是4。作用:当服务器发现已经不支持客户端使用的协议版本了,就会返回返回一个控制报文,告知客户端协议不支持,然后服务器断开连接。
连接标志:就是指明连接的一些参数。服务器必须检验连接标志中的0位(保留位)是否为0,如果不为0,服务器必须断开连接。
第1位(清理会话):是否及时清理会话状态。服务器与客户端之间是存在会话的,这个会话会保存一些信息(关于客户端),clean Session位设置为0,表示不清理会话,即保存会话状态,如果设置为1,表示要清理会话,即每一次传递数据,都是以新的会话来完成。
第2位(遗嘱标志):是否留下遗嘱消息。当客户端与服务器之间连接非正常断开之后,是否要发布一个遗嘱消息。如果Will Flag设置为1,表示要留下遗嘱消息,遗嘱消息是存储在服务器的,当客户端与服务器之间的连接异常断开后,服务器就发布此遗嘱消息;如果设置为9,表示不留下遗嘱消息。
第3、4位(遗嘱服务质量等级):发布遗嘱消息所使用的服务质量等级(0,1,2)。如果Will Flag设置为1,那么此标志才被用到,如果Will Flag设置为0,则此标志Will Qos必须为0。
第5位(遗嘱保留):是否将遗嘱消息当做保留消息发布。如果Will Flag设置为0,则Will Retain 必须设置为0.。如果Will Flag设置为1,Will Retain设置为0,则服务器将遗嘱消息当作非保留消息发布,Will Retain设置为1,则服务器将遗嘱消息当作保留消息发布。
第6位(密码标志):载荷部分是否包含密码字段。如果Password Flag设置为0,则不能包含有密码字段,设置为1,则必须包含密码字段。如果User Name Flag设置为0,那么Password Flag必须设置为0。
第7位(用户名标志):载荷部分是否包含用户名字段。如果User Name Flag设置为0,则不能包含用户名字段,设置为1,则必须包含用户名字段。
保持连接:客户端发送报文的最大间隔时间,必须不断发送,如果没有什么实际报文可发送,那可以发送一个PINGREQ报文,总之空闲时间不能大于保持连接。2个字节,16位,单位为秒。
如果保持连接不为0,那么客户端就要遵守规则,发送报文的间隔时间不能超过保持连接。如果服务器在 1.5*保持连接 的时间内没有接收到客户端的控制报文,那么就可以认为网络连接断开了,必须要断开与客户端的连接。
如果保持连接等于0,就等于没有这个规则了,服务器不需要去关心客户端是否活跃。
就是一个或者多个以长度为前缀的字段,包含的内容有哪些呢? 由可变报头中的那些标志位决定。 载荷中的内容是有严格顺序的:客户端标识符,遗嘱主题,遗嘱消息,用户名,密码。
客户端标识符:每个客户端都有能够唯一标识自己的标识符,这样服务器才能识别客户端身份。(必须存在,而且处于有效载荷的第一个字段),而且标识符的长度为1~23个字节,只能由“大写字母”、“小写字母” 和 “数字” 组成。
服务器也允许客户端传一个零字节的标识符,但是如果是这样,那么服务器必须将此看作是特殊情况,给客户端分配一个标识符, 以此假设客户端提了标识符,接着正常处理。
试想,如果客户端的标识符为零字节,服务器给客户端一个临时的标识符,那么就意味着清理会话标志必须设置为1,因为饿标识符是临时的,没法保存会话状态; 如果此时清理会话标志为0的话,服务器必须发送返回码为0x02的CONNACK报文响应客户端,然后关闭连接。
如果客户端标识符什么的,都没有问题,但是服务器就是拒绝了这个客户端,那服务器必须要发送返回码为0x02的CONNACK报文响应客户端,然后关闭连接。
遗嘱主题:如果可变报头中的遗嘱标志被设置为1,那么接下来一个字段就是遗嘱主题了。
遗嘱消息:如果可变报头中的遗嘱表示被设置为1的话,那么接下来的一个字段就是遗嘱消息。
用户名:如果可变报头中的用户名标志被设置为1的话,那么接下来一个字段就是用户名。
密码:如果可变报头中的密码标志被设置为1的话,那么接下来一个字段就是密码。包含两个字节的“长度”,和密码部分。
网络基础设施建立好了之后,即客户端可以与服务器网络沟通之后,如果在规定的时间内没有收到CONNECT报文,服务器可以关闭这个连接。
服务器必须按照相应的要求去验证CONNECT报文,如果报文不符合要求,服务器不发送CONNACK报文,直接关闭连接。
服务器可以做数据的业务检查,如果发现CONNECT报文中的内容不满足要求,可以发送一个适当的CONNACK响应,并且必须关闭连接。
CONNACK报文是专门用于响应用户的CONNECT连接请求报文的,这是服务器发送给客户端的第一个报文。如果客户端从发送完CONNECT报文后的一段合理的时间内,没有接收到服务器的CONNACK报文,客户端应该关闭连接。
报文的固定报头的格式肯定都是一样的,只是内容不一样,CONNACK报文的固定报头内容如下,报文类型是2,剩余长度固定是2。既然剩余长度为2,那么我们接下来就看看这两个字节到底存的是什么(可变报头的两个字节,没有有效载荷)。
可以看出,CONNACK报文的可变报头中的内容就比较少了,没有CONNECT报文那么多,至于两个内容,连接确认标志和连接返回码,每个内容占1个字节。
连接返回码:先说这个返回码,返回码是1个字节,无符号值。规定了一些返回码,每一个返回码都代表一个意义。服务器如果向客户端返回的CONNACK报文中的返回码是非零的,服务器必须关闭与客户端的连接。
如果实际的场景与下表中所有的返回码意义都不符合,服务器必须关闭网络连接,不需要发送CONNACK报文。
值 | 返回码响应 | 描述 |
0 | 0x00 表示接受连接 | 服务器已接受客户端的CONNECT请求连接 |
1 | 0x01 表示拒绝连接,不支持协议版本 | 服务器不支持客户端的MQTT协议版本 |
2 | 0x02 表示拒绝连接,不合格的客户端标识符 | 服务器认为客户端标识符不合格 |
3 | 0x03 表示拒绝连接,服务器不可用 | 网络连接已建立,但是MQTT服务不可用,问题出在服务器上 |
4 | 0x04 表示拒绝连接,无效的用户名或者密码 | 用户名或密码的数据格式无效(注意是格式) |
5 | 0x05 表示拒绝连接,未授权 | 客户端未被授权连接到此服务器 |
6~255 | 暂时没有使用 | 保留 |
连接确认标志:1个字节,共8位,1~7位均为0(保留位),0位才是有用的那一位,0位是“当前会话标志”,那这个当前会话标志有什么作用呢?首先理解下面的一段话:
客户端发送CONNECT连接请求报文,如果报文中的清理会话标志为1,那CONNACK报文的返回码要设置为0,当前会话标志要设置为0。 如果CONNECT报文中的清理会话标志为0,此时就取决于服务器是否已经保存了客户端的会话状态,如果没有存储,那服务器必须将CONNACK报文中的当前会话标志设置为0,将返回码设置为0; 如果服务器存储了客户端的会话状态,那必须将CONNACK报文中的当前会话标志设置为1。
通过上面一段的说明,当前会话标志的作用就是要告诉客户端,我服务器是不是已经存储有我们的会话状态。这样的话,如果服务器返回的会话状态标志为1,就说明服务器存有会话状态,客户端也可以存储会话状态呀,此时客户端就可以判断服务器的行为是否合理,如果不合理,就可以断开。(之前总是服务器去各种验证,检查客户端,现在客户端也可以有点验证服务器的地方了)。
注意:如果服务器发送一个非零返回码的CONNACK报文,必须将当前会话标志设置为0。
CONNACK报文没有有效载荷。
PUBLISH报文的类型是3,DUP是重发标志,Qos是服务质量等级,RETAIN是保留标志(是有用的,不是那种保留在哪将来用,这个标志的名字就是保留标志)。