MQTT协议其实还是挺简单的,从为数不多的项目中,简单的总结了下MQTT 3.1.1协议某些字段的特性。
MQTT 5.0的特性在 https://blog.csdn.net/mrpre/article/details/87267400 中介绍
包括 Will topic 、will message 、 will Qos、will retain。
一个Client异常断开连接的时候或者Server处理失败的时候,Server会把Client的will信息,当做Publish处理,Publish的topic就是Will topic,消息就是will message。
will Qos 见下文的Qos,will retain见下文的retain。
如果Client发送了disconnect来断开连接,则Server就会清除will信息,不会把will当做Publish处理。
Retain 表示 Publish 的消息,会保留在 Broker上,一旦有其他新的连接上来,并且subscribe了对应的消息,会立刻Publish这个Retain的消息。且回复给订阅者的的这个Publish也会携带Retain标志位。
MQTT规范上说,这个功能,对于发布的消息是自己状态的话,就很有用。
由于Will消息中也携带retain,且Will消息本身会也当做Publish,所以Retain的效果也作用于will,也就是说携带Retain的Will消息会被Server保留,且对于新连接,同样也会尝试去publish(如果topic匹配)。
其次,Client可以多次通过发送Retain标志位的Publish,来更新Server保存的Publish消息,当然Server是保留client发送的所有的Retain消息的,而不是覆盖之前你的Retain,不过回复订阅者的是最新消息(我不是很理解MQTT为什么要保留老的,既然发送给客户端的永远是最新的那个)。
Client可以通过发送Retain标志位的,且没有message的Publish,来告知Server删除这个Retain消息,注意,是清空所有该topic的消息。
Client可以通过发送Retain标志位的,Qos为0的Publish,来告知Server删除对应Topic的所有Retain消息,且保留当前这个消息为Retain的消息。
如果Connect
帧的CleanSession为0,则Server会查找保存上次该客户端连接过来的Session,session里面保存了之前Client携带的所有的订阅topic等,如果Server找到了session,会在Connect Ack
中把session present
置1,否则置0。换句话说,如果成功用老的会话,Client不需要重新订阅之前的Topic。
CleanSession的另一个好处就是Server保留了Qos信息,例如,假设Client上次收到Publish消息,但是还没来得及回复Publish ack
或者Publish recvive
就挂了,Server知道Client还没处理,会在Client第二次连接完成后立刻再次发送Publish消息。(前提是Client的CleanSession为1,且Server找到了Session)。
Connect
帧中携带这个,这个值是Client发送Ping request
的间隔。Client保证keep alive时间之内都有帧,如果没有控制帧可发,就发Ping request
,Server必须回复Ping response
以表示自己是活着的。
主要分为订阅的Qos,表示的是订阅者期望的自己收到消息的级别,另一个是推送一方的Qos,表示推送方指示的该消息的级别。
换句话说,Qos代表了消息的重要性,订阅方认为的以及推送方认为的。所以,当消息推送的订阅方时3,使用的Qos是 min(订阅方Qos, 推送方Qos)
。
就是MQTT协议长度太小了,小到太适合嵌入式设备的运行了,小到太节省带宽,小到没有任何拓展
字段,当我尝试在publish
或者subscribe
中加入一些私有信息时,没有找到任何可以允许添加的位置,最终以失败而告终。
注:在MQTT 5.0中已经解决 https://blog.csdn.net/mrpre/article/details/87267400
ACK没有错误标志位。 协议中没有定义publish
或者subscribe
失败时,对应的ack如何表示失败。单纯的不回复ack,会导致对端重传publish
或者subscribe
,或者对端进程hang住。
注:在MQTT 5.0中已经解决 https://blog.csdn.net/mrpre/article/details/87267400
分布式痛点也存在。MQTT作为一个轻量级的应用层协议,当分布式模式存在时,其实现就不再轻量级了,开源的Mosquitto是通过bridge方式进行分布式的,并不是很合理的方式。合理的方式就是MQTT server集群携带一个中心化存储,类似redis,用来保存session等信息,MQTT server来读取。但是这种RPC调用就复杂了,用C/C++实现非常耗时间。从各大云厂商公布的Iothub
性能来看,也基本能猜到都是用JAVA实现的,而性能就差很多了。
当然,目前还有一些疑问,MQTT明明是基于TCP的,为何怕传输丢失
然后要有重传
的功能?我在协议中找到如下一句话:
Historically retransmission of Control Packets was required to overcome data loss on some older TCP networks.
This might remain a concern where MQTT 3.1.1 implementations are to be deployed in such environments.
即使MQTT客户端是嵌入式设备,但是依赖的TCP总不是不可靠传输协议吧?
其次,我能想到所谓丢失
,一般MQTT的模式是broker对应多个client,一个client publish
消息,然后broker全权代理,收到消息这个推送消息后,broker会发送到匹配订阅topic的所有client,那么,即使对应的client在tcp层回复了ack,但是订阅者应用层没有read,即业务层没有处理,此时恰巧订阅者挂了(断电),这应该也算是丢失
的情况吧,但是这种丢失
,我个人觉得能解决的方法就是订阅者的cleansession
为0,那么订阅者第二次连接(重启或者加电)的时候,broker会重发(前提是这个丢失
的消息,Qos是1或者2,且第一次发时,client没有完成Qos 1或者2的握手)。
排除TCP层不可靠的原因,MQTT的丢失
和重传
,岂不是和cleansession
绑定在一起了?即Qos 1 和 2的效果,本质上需要结合cleansession
为0来使用?
顺便吐槽一下,MQTT规范写的真是。。。怪不得进不了IEFT。