文章目录
前言
本篇博文主要是介绍第三报文,即PUBLISH报文,用于客户或服务端对服务或客户端间传输信息。
PUBLISH – 发布消息
PUBLISH报文是指从客户端向服务端或者服务端向客户端传输一个应用消息。
PUBLISH报文固定报头
MQTT 5中,剩余长度不止一个字节
重发标志
位置:第1个字节,第3位
如果DUP标志被设置为0,表示这是客户端或服务端第一次请求发送这个PUBLISH报文。如果DUP标志被设置为1,表示这可能是一个早前报文请求的重发。
客户端或服务端请求重发一个PUBLISH报文时,必须将DUP标志设置为1。对于QoS 0的消息,DUP标志必须设置为0。
服务端发送PUBLISH报文给订阅者时,收到(入站)的PUBLISH报文的DUP标志的值不会被传播。发送(出站)的PUBLISH报文与收到(入站)的PUBLISH报文中的DUP标志是独立设置的,它的值必须单独的根据发送(出站)的PUBLISH报文是否是一个重发来确定。
接收者收到一个DUP标志为1的控制报文时,不能假设它看到了一个这个报文之前的一个副本
需要特别指出的是,DUP标志关注的是控制报文本身,与它包含的应用消息无关。当使用QoS 1时,客户端可能会收到一个DUP标志为0的PUBLISH报文,这个报文包含一个它之前收到过的应用消息的副本,但是用的是不同的报文标识符。
服务质量等级
位置:第1个字节,第2-1位。
这个字段表示应用消息分发的服务质量等级保证。
服务质量等级在下表中列出。
【MQTT3.1.1】
PUBLISH报文不能将QoS所有的位设置为1。如果服务端或客户端收到QoS所有位都为1的PUBLISH报文,它必须关闭网络连接。
【MQTT 5】
如果服务端在对客户端响应的CONNACK报文中包含了最大服务质量(Maximum QoS)且服务端收到的PUBLISH报文的QoS大于此最大服务质量,服务端发送包含原因码为0x9B(不支持的QoS等级)的DISCONNECT报文。
PUBLISH报文的2个QoS比特位不能同时设置为1。如果服务端或客户端收到QoS 2个比特位都为1的无效PUBLISH报文,使用包含原因码为0x81(无效报文)的DISCONNECT报文关闭网络连接。
保留标志
位置:第1个字节,第0位。
如果客户端发给服务端的PUBLISH报文的保留(RETAIN)标志被设置为1,服务端必须存储这个应用消息和它的服务质量等级(QoS),以便它可以被分发给未来的主题名匹配的订阅者。
MQTT 5中,会将储存的应用信息替换此话题下任何已存在的消息。而且如果载荷为空,消息可以正常被服务端所处理,但是此话题下的任何保留消息必须被丢弃,并且此话题未来的订阅者将不会收到保留消息。载荷为空的保留消息将不能被存储在服务端。
【MQTT3.1.1】
-
一个新的订阅建立时,对每个匹配的主题名,如果存在最近保留的消息,它必须被发送给这个订阅者 。
-
如果服务端收到一条保留(RETAIN)标志为1的QoS 0消息,它必须丢弃之前为那个主题保留的任何消息。它应该将这个新的QoS 0消息当作那个主题的新保留消息,但是任何时候都可以选择丢弃它 — 如果这种情况发生了,那个主题将没有保留消息 。
MQTT 5也是如此,当服务端收到保留标志设置为1且QoS设置为0的PUBLISH报文,会把此QoS为0的消息存储为其主题下最新的保留消息,服务端可以选择在任何时间丢弃此消息。如果发生丢弃,该主题下将不存在任何保留消息。
-
服务端发送PUBLISH报文给客户端时,如果消息是作为客户端一个新订阅的结果发送,它必须将报文的保留标志设为1。当一个PUBLISH报文发送给客户端是因为匹配一个已建立的订阅时,服务端必须将保留标志设为0,不管它收到的这个消息中保留标志的值是多少。
-
保留标志为1且有效载荷为零字节的PUBLISH报文会被服务端当作正常消息处理,它会被发送给订阅主题匹配的客户端。此外,同一个主题下任何现存的保留消息必须被移除,因此这个主题之后的任何订阅者都不会收到一个保留消息。当作正常(As normal)意思是现存的客户端收到的消息中保留标志未被设置。服务端不能存储零字节的保留消息。
-
如果客户端发给服务端的PUBLISH报文的保留标志位0,服务端不能存储这个消息也不能移除或替换任何现存的保留消息。
与MQTT 5一致
【MQTT 5】
如果服务端发送给客户端的CONNACK报文中包含保留可用属性,且属性值为0,但收到的PUBLISH报文中保留标志位为1,服务端使用包含原因码为0x9A(保留不支持)的DISCONNECT报文断开网络连接。
当一个新的非共享订阅(Non-shared Subscription)被创建时,每个匹配的话题下的最新保留消息如果存在,将根据保留消息订阅选项(Retain Handling Subscription Option)发送给客户端。这些消息在发送时保留标志被设置为1。保留消息的发送由保留消息处理订阅选项控制,收到订阅时:
- 如果保留消息处理属性被设置为0,服务端必须发送主题与客户端订阅的主题过滤器(Topic Filter)相匹配的所有保留消息 。
- 如果保留消息处理属性被设置为1,如果尚不存在匹配的订阅,服务端必须发送主题与客户端订阅的主题过滤器相匹配的所有保留消息。如果已存在相匹配的订阅,服务器不能发送这些保留消息
- 如果保留消息处理属性被设置为2,服务器不能发送这些保留消息
如果服务端收到保留标志设置为1且QoS设置为0的PUBLISH报文,服务端应该把此QoS为0的消息存储为其主题下最新的保留消息,但服务端可以选择在任何时间丢弃此消息。如果发生丢弃,该主题下将不存在任何保留消息。
如果某个主题当前的保留消息过期,该主题下将不存在任何保留消息。
MQTT3.1.1没有这个说明,MQTT 5则有消息过期间隔设置
服务端转发应用消息时,保留标志位的设置由发布保留(Retain As Published)订阅选项决定。
- 如果发布保留(Retain As Published)订阅选项被设置为0,服务端在转发应用消息时必须将保留标志设置为0,而不管收到的PUBLISH报文中保留标志位如何设置的
- 如果发布保留(Retain As Published)订阅选项被设置为1,服务端在转发应用消息时必须将保留标志设置为与收到的PUBLISH消息中的保留标志位相同
对于发布者不定期发送状态消息这个场景,保留消息很有用。新的非共享订阅者【针对MQTT 3来说,MQTT3.1.1的所有订阅者,在MQTT 5中都被认为是非共享订阅者】将会收到最近的状态
剩余长度
等于可变报头的长度加上有效载荷的长度,被编码为变长字节整数。
可变报头
PUBLISH报文可变报头按顺序包含:主题名(Topic Name),报文标识符(Packet Identifier),属性(Properties)。
MQTT3.1.1没有属性
主题名
主题名(Topic Name)用于识别有效载荷数据应该被发布到哪一个信息通道。
主题名必须是PUBLISH报文可变报头的第一个字段。它必须是 UTF-8编码的字符串。
PUBLISH报文中的主题名不能包含通配符。
服务端发送给订阅客户端的PUBLISH报文的主题名必须匹配该订阅的主题过滤器。
MQTT 5因为存在可以设置主题别名的情况下,后续操作略有不同。
【MQTT 5】
由于服务端允许将主题名映射为其他名字,主题名可能与原始PUBLISH报文中的主题名不同。
发送端可以使用主题别名(Topic Alias)以便减少PUBLISH报文的长度。
主题名长度为0且没有主题别名,将造成协议错误(Protocol Error)。
报文标识符
只有当QoS等级是1或2时,报文标识符(Packet Identifier)字段才能出现在PUBLISH报文中。
PUBLISH属性(MQTT 5)
属性长度
PUBLISH报文可变报头中的属性长度被编码为变长字节整数。
载荷格式指示
1 (0x01)Byte,载荷格式指示(Payload Format Indicator)标识符。
跟随其后的是单字节的载荷格式指示值,可以是:
- 0 (0x00),说明载荷是未指定格式的字节,相当于没有发送载荷格式指示。
- 1 (0x01),说明载荷是UTF-8编码的字符数据
服务端必须把接收到的应用消息中的载荷格式指示原封不动的发给所有的订阅者。接收者可以验证载荷数据与所指示的格式一致,如果不一致,发送包含原因码为0x99(载荷格式无效)的PUBACK,PUBREC或DISCONNECT报文。
消息过期间隔
2 (0x02)Byte,消息过期间隔(Message Expiry Interval)标识符。
跟随其后的是四字节整数表示的消息过期间隔(Message Expiry Interval)。
如果消息过期间隔存在,四字节整数表示以秒为单位的应用消息(Application Message)生命周期。如果消息过期间隔(Message Expiry Interval)已过期,服务端还没开始向匹配的订阅者交付该消息,则服务端必须删除该订阅者的消息副本。
如果消息过期间隔不存在,应用消息不会过期。
服务端发送给客户端的PUBLISH报文中必须包含消息过期间隔,值为接收时间减去消息在服务端的等待时间。
主题别名
35 (0x23)Byte,主题别名(Topic Alias)标识符。
跟随其后的是表示主题别名(Topic Alias)值的双字节整数。包含多个主题别名值将造成协议错误(Protocol Error)。
主题别名是一个整数,用来代替主题名对主题进行识别。主题别名可以减小PUBLISH报文的长度,这对某个网络连接中发送的很长且反复使用的主题名来说很有用。
发送端决定是否使用主题别名及别名值如何选取。发送端通过在PUBLISH报文中包含的非0长度主题名和主题别名来设置主题别名映射。接收端正常处理该PUBLISH报文,但同样将指定的主题别名映射到主题名。
如果接收端已经设置了某个主题别名映射,发送端可以发送包含主题别名和长度为0的主题名的PUBLISH报文。接收端把此PUBLISH报文的主题名当做其包含的主题别名所映射的主题名。
发送端可以通过在同一个网络连接中发送另一个包含同样主题别名和不同非0长度主题名的PUBLISH报文来修改主题别名映射关系。
主题别名映射仅作用于某个网络连接及其生命周期内。接收端不能将任何主题别名映射从一个网络连接转发到另一个网络连接。
主题别名不允许为0。发送端不能发送包含主题别名值为0的PUBLISH报文。
客户端不能发送主题别名值大于服务端的CONNACK报文中指定的主题别名最大值(Topic Alias Maximum)的PUBLISH报文。客户端必须接受所有值大于0且小于等于其发送的CONNECT报文中的主题别名最大值的主题别名。
服务端不能发送包含主题别名值大于客户端在CONNECT报文中指定的主题别名最大值(Topic Alias Maximum)的PUBLISH报文。服务端必须接受所有值大于0且小于等于其发送的CONNACK报文中的主题别名最大值的主题别名。
客户端和服务端使用的主题别名映射相互独立。因此一般来说,客户端发送给服务端的主题别名值为1的PUBLISH报文和服务端发送给客户端的主题别名值为1的PUBLISH报文,将被映射到不同的主题。
响应主题
8 (0x08)Byte,响应主题(Response Topic)标识符。
跟随其后的是一个UTF-8编码的字符串,用作响应消息的主题名。响应主题必须是UTF-8编码的字符串 。响应主题不能包含通配符。包含多个响应主题将造成协议错误(Protocol Error)。响应主题的存在将消息标识为请求报文。
服务端在收到应用消息时必须将响应主题原封不动的发送给所有的订阅者。
包含响应主题的应用消息接收端使用响应主题作为主题名,发送作为响应消息的PUBLISH报文。如果请求消息中包含对比数据,接收端应当在发送作为对此请求消息进行响应的PUBLISH报文中包含此对比数据。
对比数据
9 (0x09)Byte,对比数据(Correlation Data)标识符。
跟随其后的是二进制数据。对比数据被请求消息发送端在收到响应消息时用来标识相应的请求。包含多个对比数据将造成协议错误(Protocol Error)。如果没有设置对比数据,则请求方(Requester)不需要任何对比数据。
服务端在收到应用消息时必须原封不动的把对比数据发送给所有的订阅者。对比数据只对请求消息(Request Message)的发送端和响应消息(Response Message)的接收端有意义。
接收端收到包含响应主题和对比数据的应用消息时,发送以响应主题为主题名的PUBLISH报文作为响应消息。客户端在响应消息中应将对比数据作为PUBLISH报文的一部分原封不动的发送出去。
如果对客户端响应消息中的对比数据所做的任何更改会造成应用程序错误,则应当对对比数据进行加密/哈希,以便接收端能检测到对比数据是否被更改。
用户属性
38 (0x26)Byte,用户属性(User Property)。
跟随其后的是UTF-8字符串键值对。用户属性(User Property)允许出现多次,以表示多个名字/值对,且相同的名字可以多次出现。
服务端在转发应用消息到客户端时必须原封不动的把所有的用户属性放在PUBLISH报文中。服务端在转发应用消息时必须保持所有用户属性的先后顺序。
此属性旨在提供一种传递应用层名称-值标签的方法,其含义和解释仅由负责发送和接收它们的应用程序所有。
订阅标识符
11 (0x0B)Byte,订阅标识符(Subscription Identifier)标识符。
跟随其后的是一个变长字节整数表示的订阅标识符。
订阅标识符取值范围从1到268,435,455。订阅标识符的值为0将造成协议错误。如果某条发布消息匹配了多个订阅,则将包含多个订阅标识符。这种情况下他们的顺序并不重要。
内容类型
3 (0x03)Byte, 内容类型(Content Type)标识符。
跟随其后的是一个以UTF-8格式编码的字符串,用来描述应用消息的内容。内容类型必须是UTF-8编码的 字符串。 包含多个内容类型将造成协议错误(Protocol Error)。内容类型的值由发送应用程序和接收应用程序确定。
服务端必须把收到的应用消息中的内容类型原封不动的发送给所有的订阅者。
UTF-8编码字符串可以使用一个MIME内容类型字符串来描述应用消息的内容。由于发送程序和接收程序负责内容类型字符串的定义和解释,因此MQTT服务端只确保内容类型是有效的UTF-8编码的字符串,不会做其他方面的验证。
下图是展示了两个版本的一个PUBLISH示例报文,其中主题名为a/b,报文标识符为10,没有属性【针对 MQTT 5】。
MQTT3.1.1的示例中的主题名为 “a/b”,长度等于3,报文标识符为 “10”
有效载荷
有效载荷包含将被发布的应用消息。数据的内容和格式是应用特定的。有效载荷的长度这样计算:用固定报头中的剩余长度字段的值减去可变报头的长度。包含零长度有效载荷的PUBLISH报文是合法的。
响应
PUBLISH报文的接收者必须按照根据PUBLISH报文中的QoS等级发送响应,见下面表格的描述.
PUBLISH报文的预期响应
动作
-
客户端使用PUBLISH报文发送应用消息给服务端,目的是分发到其它订阅匹配的客户端。
-
服务端使用PUBLISH报文发送应用消息给每一个订阅匹配的客户端。
MQTT 5 中,如果有的话,PUBLISH数据报文包括SUBSCRIBE数据报文中携带的订阅标识符。
-
客户端使用带通配符的主题过滤器请求订阅时,客户端的订阅可能会重叠,因此发布的消息可能会匹配多个主题过滤器。这种情况下,服务端必须按照所有匹配的订阅中最大的QoS等级把消息发送给客户端。此外,服务端可以为每一个匹配的订阅按照订阅时的QoS等级,把消息副本分发给客户端。
-
收到一个PUBLISH报文时,接收者的动作描述的QoS等级
-
==【MQTT 3.1.1】==如果服务端实现不授权某个客户端发布PUBLISH报文,它没有办法通知那个客户端。它必须按照正常的QoS规则发送一个正面的确认,或者关闭网络连接。
无法从官方规范文档中确认MQTT 5 是否存在该动作
【MQTT 5】
MQTT 5特有的动作
-
如果客户端收到一个未经请求的应用消息(没有匹配任何订阅),且QoS大于客户端指定的最大服务质量(Maximum QoS),客户端使用包含原因码为0x9B(不支持的QoS等级)的DISCONNECT报文断开连接。
-
如果客户端在这些重叠的订阅中指定了订阅标识符,服务端在发布这些订阅相匹配的消息时必须包含这些订阅标识符。如果服务端对这些重叠的订阅只发送一条相匹配的消息,服务端必须在PUBLISH报文中包含所有的相匹配的订阅标识符(如果存在),但没有顺序要求。如果服务端对这些重叠的订阅必须分别发送相匹配的消息,则每个PUBLISH报文中含与订阅相匹配的订阅标识符(如果存在)。
-
可能存在客户端对同一个发布消息做了多次订阅,并且这些订阅中有多个订阅使用了相同的订阅标识符,这种情况下PUBLISH报文将携带多个相同的订阅标识符。
-
PUBLISH报文中若包含服务端收到的SUBSCRIBE报文以外的订阅标识符,将造成协议错误(Protocol Error)。从客户端发送给服务端的PUBLISH报文不能包含订阅标识符。
-
对于共享订阅,发送给某个客户端的PUBLISH报文中将只包含该客户端的SUBSCRIBE报文中发送的订阅标识符。
-
如果PUBLISH报文包含主题别名,接收端按照以下方式进行处理:
-
主题别名为0或大于最大主题别名(Maximum Topic Alias),将造成协议错误(Protocol Error),收端使用包含原因码为0x94(主题别名无效)的DISCONNECT报文断开网络连接。
-
如果接收端已创建此主题别名的映射,
a) 如果报文包含的主题名长度为0,接收端使用主题别名对应的主题名处理此报文
b) 如果报文包含的主题名长度不为0,接收端使用此主题名处理此报文,并更新此主题别名映射到此主题名
-
如果接收端还没有创建此主题别名的映射,
a) 如果报文包含的主题名长度为0,将造成协议错误,接收端使用包含原因码为0x82(协议错误)的DISCONNECT报文断开网络连接
b) 如果报文包含的主题名长度不为0,接收端使用此主题名处理此报文,并为此报文中的主题别名和主题名创建映射关系
-
如果服务端向客户端分发应用消息时使用了不同的协议级别(比如MQTT v3.1.1)-- 不支持属性或MQTT 5提供的其他功能,应用消息中的某些信息将丢失,依赖于这些信息的应用程序可能无法正常工作。
-
客户端在收到服务端的PUBACK,PUBCOMP或包含原因码大于等于128的PUBREC报文之前,不能发送数量超过服务端的接收最大值(Receive Maximum)的QoS为1和2的PUBLISH报文。服务端在发送PUBACK或PUBCOMP响应之前,如果收到数量超过客户端的接收最大值的QoS为1和2的PUBLISH报文,服务端使用包含原因码为0x93(超出接收最大值)的DISCONNECT报文断开网络连接。
-
客户端不能延迟发送任何报文,除了PUBLISH报文–如果已发送且没有收到确认的PUBLISH报文数量已达到服务端的接收最大值(Receive Maximum)。接收最大值只应用于当前网络连接。
客户端可以选择发送少于服务端接收最大值的未经确认的PUBLISH报文,尽管它可以发送更多数量的报文。
客户端可以选择暂停发送QoS为0的报文,当其暂停发送了QoS为1和2的PUBLISH报文。
如果客户端在收到CONNACK之前发送QoS为1或QoS为2的PUBLISH报文,客户端有可能被服务器断开连接,因为它发送了超过服务端接收最大值数量的发布报文。
-
服务端在接收到客户端的PUBACK,PUBCOMP或包含原因码大于等于128的PUBREC报文之前,不能发送数量超过客户端的接收最大值(Receive Maximum)的QoS为1和2的PUBLISH报文。客户端在发送PUBACK或PUBCOMP响应之前,如果收到数量超过服务端的接收最大值的QoS为1和2的PUBLISH报文,客户端使用包含原因码为0x93(超出接收最大值)的DISCONNECT报文断开网络连接。
-
服务端不能延迟发送任何报文,除了PUBLISH报文–如果已发送且没有收到确认的PUBLISH报文数量已到达客户端的接收最大值(Receive Maximum)
服务端可以选择发送少于客户端接收最大值的未经确认的PUBLISH报文,尽管它可以发送更多数量的报文。
服务端可以选择暂停发送QoS为0的报文,当其暂停发送了QoS为1和2的PUBLISH报文。
总结
PUBLISH报文涉及到相应的服务质量等级确认,下一篇描述 PUBACK ,这是对QoS1等级的PUBLISH报文的响应报文。