mqtt属于应用层协议,在tcp上层,mqtt协议中的用户角色分为两类mqtt_broker(中心转发)、mqtt_client(pub/sub)。
这两者之间的消息交互分为如下几类:连接/断开服务器、订阅/取消订阅、发布消息、心跳。
CONNECT报文的可变报文头按下列次序包含四个字段:协议名、协议级别、连接标志、保持连接。
连接标志
清理会话 Clean Session:
指定会话状态处理方式,客户端和服务端可以保存会话状态,以支持跨网络连接的可靠消息传输。
设置为 0:当连接断开后,客户端和服务端必须保存会话信息,以便重新连接后使用。
设置为 1:当连接断开后,丢弃之前会话信息。
这里的会话信息涉及到很多状态,比如:
客户端的会话状态:
服务端的会话状态:
遗嘱标志 Will Flag:
遗嘱标志被设置为 1,表示如果连接请求被接受了,遗嘱(Will Message)消息必须被存储在服务端并且与这个网络连接关联。之后网络连接关闭时,服务端必须发布这个遗嘱消息,除非服务端收到 DISCONNECT 报文时删除了这个遗嘱消息 。
遗嘱消息发布的条件,包括但不限于:
遗嘱 QoS Will QoS:
这两位用于指定发布遗嘱消息时使用的服务质量等级。
遗嘱保留 Will Retain:
当客户端连接断开,遗嘱消息发布后,该消息会保留在服务器上。这样,任何在此后订阅相应主题的客户端,都能收到这条遗嘱消息,了解到该客户端已经下线的信息。这对于需要监控设备或用户在线状态的应用来说非常有用。
用户名标志 User Name Flag:
标识 payload 中是否需要包含用户名。
密码标志 Password Flag:
标识 payload 中是否需要包含密码信息。
保持连接 Keep Alive:
保持连接是一个以秒为单位的时间间隔,它是指在客户端传输完成一个控制报文的时刻到发送下一个报文的时刻,两者之间允许空闲的最大时间间隔。客户端负责保证控制报文发送的时间间隔不超过保持连接的值。
如果设置了保持连接,并且客户端在这个时间内并未发送任何报文,服务端可以断开客户端连接。
CONNECT 报文的有效载荷(payload)包含:客户端标识符、遗嘱主题、遗嘱消息、用户名、密码。通过上面说的包头中连接标志来决定是否包含这些字段。
客户端标识符
用于识别连接到 MQTT 服务器的客户端。每个客户端在连接一个目标服务器里都必须具有唯一的标识符,以便服务器可以区分它们。
如果两台设备都使用相同的客户端标识符,则会造成之后上线的设备连接服务器后,服务器将主动断开前一台设备的连接。
断开连接
DISCONNECT:表示客户端正常断开连接,服务端收到后需要清理与当前连接相关的未发布的遗嘱消息。
SUBSCRIBE报文的有效荷载包含了主题过滤器列表、qos。
PUBLISH控制报文是指从客户端向服务器或者服务器向客户端发送一个应用消息,其实从服务器分发的报文给订阅者也属于PUBLISH控制报文。
**注意:**不同的qos,publish控制报文的处理方式也不同,publish和subscribe这两个消息中都会带qos,mqtt协议规定:无论是发布消息的qos等级还是订阅消息的qos等级,实际的qos等级都会以两者中的最小值为准。
qos等级:
qos0代表消息的分发依赖于底层网络的能力,服务器不会发送响应,发布者也不会重试,它在发出这个消息的时候就会立马将消息丢弃,这个消息可能送达一次也可能根本没送达。
qos1表示服务质量确保消息至少送达一次,甚至可能被多次处理。qos1的publish报文的可变报文头中包含一个报文标识符,需要PUBACK报文确认。
qos1的场景会带来什么问题?
消息重复处理,比如发送了publish消息,并且接收端也回复了puback,但是由于网络原因,puback晚到了,导致发送端由于timeout重新触发了publish消息,这个时候接收端仍旧会处理这条重复的publish消息。
qos2表示消息既不丢失也不会重复。
为啥需要类似四次握手才能保证消息不丢失也不会重复?
我们先看只采用两次交互:
两次交互如何保证消息不丢失不重复?
两次交互的缺点:
采用三次交互:
三次交互的缺点:
release消息无法保证一定能被server和sub接收到,因此还需要一次响应来保证server、sub接收到release消息。
采用四次交互:
四次交互在三次交互基础上增加了一个pubcomp包,当pub与server收到pubcomp包后,表示接收端已经收到release包了,可以删除msg了。到此qos2实现了包的去重与msgid的可重用。
mqtt协议规定了PINGREQ和PINGRESP消息类型,用于实现心跳机制。PINGREQ消息是由客户端向服务器发送的心跳包,PINGRESP是服务器回复的心跳包确认消息。
在mqtt协议中,PINGREQ是在一段时间内没有任何其他报文交互时才会发送,这个机制类似内核tcp层的keepalive。
当客户端和服务器建立连接时,会设定一个keepalive的时间间隔,如果在这个时间间隔内,客户端没有发送或接收任何其他报文,那么就会发送一个PINGREQ到服务器,以保持连接的活跃。
所以PINGREQ并不是定时发送,而是在没有其他报文交互的情况下才会发送,以此来保持与服务器的连接。