##mqtt协议:万物互联
在20世纪90年代中期IBM在帮助石油和天然气公司客户设计有效的数据传输协议时,就出现了对MQTT这种物联网环境下的数据传输协议的需求。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FFx2BXWd-1572514144487)(./1567662845623.png)]
当时这种应用场景有如下几个特点:
石油天然气管道线路非常长,要接许多沿线的数据采集网关;服务器要能接成千上万个通信客户端;石油管道传感器的数据采集频率不高,不需要一下子传输大量数据;现场采集网关由于量大,考虑到采购成本,CPU和存储等计算资源都很有限;石油管道会穿越很多无人区,附近没有网络设施,因此使用卫星通讯最为经济;高轨道的GEO卫星站得高看得远,覆盖范围广,但轨道高延迟就大了。中低轨道的LEO/MEO卫星延迟小,但是覆盖区域有限,每天都会出现卫星切换时的网络中断。因此需要客户端和服务器端都能够保留消息收发状态,在网络恢复正常后继续发送;而且卫星链路带宽低(当然也有高带宽的),通信流量费用高昂;因此需要尽量节省传输的消息的流量开销;有些数据发送失败,不需要重发。但是有些消息比如阀门泄露告警或控制石油管道阀门的命令,就必须要在网络有问题的情况下也要能确保发送成功。因此,需要设计这样一种通讯协议:
服务器要能接成千上万个客户端;每次消息传输的数据量不大;协议客户端软件要能在CPU和存储等计算资源都很有限的单片机、单板机、RTU等上运行;并能方便的实现移植到不同的硬件上;带宽低,通信流量费用高昂;需要最大限度地减少传输消息大小;网络不稳定。 卫星不会24小时都覆盖得到,会有段时间发生卫星通信中断;预期会遇到频繁的网络中断(低带宽,高延迟,不可靠,高成本运行的网络)。需要传输协议能够异步管理消息。在卫星通信中断时:MQTT网络中的服务器/代理可以保存和转发从客户端到客户端的消息,如果断开连接,它将能够在以后重新连接时获取消息。在环境允许的情况下提供传统的消息服务质量。提供不同等级的“服务质量”。mqtt便由此诞生。
mqtt是一种在低带宽高延迟不可靠的网络下,进行数据相对可靠传输的应用层协议
它简单、适应物联网环境
它以消息为单位进行传输
在低带宽、高延迟、不可靠的网络,和资源有限的硬件环境下,进行相对可靠的数据传输。 对于可靠性,mqtt提供三种可选的消息发布的服务等级,供开发人员在相应的场景下自行选用。
mqtt基于tcp协议,属于应用层(7层)协议,与http协议处于同一层级。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4fdlSCeb-1572514144488)(./1567481761389.png)]
mqtt采用发布/订阅模式。
一个典型的mqtt架构如下图: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ezRnnlNb-1572514144489)(./1570414600935.png)]
它带来了这些好处:
发布者与订阅者不必了解彼此,只要认识同一个消息代理即可。 发布者和订阅者不需要交互,发布者无需等待订阅者确认而导致锁定。 发布者和订阅者不需要同时在线,可以自由选择时间来消费消息。
MQTT是通过主题对消息进行分类的,本质上就是一个UTF-8的字符串,不过可以通过反斜杠表示多个层级关系。主题并不需要创建,直接使用就是了。
主题还可以通过通配符进行过滤。其中,+可以过滤一个层级,而#只能出现在主题最后表示过滤任意级别的层级。
举个例子:
building-b/floor-5:代表B楼5层的设备。 +/floor-5:代表任何一个楼的5层的设备。 building-b/#:代表B楼所有的设备。 注意,MQTT允许使用通配符订阅主题,但是并不允许使用通配符广播。
为了满足不同的场景,MQTT支持三种不同级别的服务质量(Quality of Service,QoS)为不同场景提供消息可靠性:
级别0:尽力而为。消息发送者会想尽办法发送消息,但是遇到意外并不会重试。 级别1:至少一次。消息接收者如果没有知会或者知会本身丢失,消息发送者会再次发送以保证消息接收者至少会收到一次,当然可能造成重复消息。 级别2:恰好一次。保证这种语义肯定会减少并发或者增加延时,不过丢失或者重复消息是不可接受的时候,级别2是最合适的。 服务质量是个老话题了。级别2所提供的不重不丢很多情况下是最理想的,不过往返多次的确认一定对并发和延迟带来影响。级别1提供的至少一次语义在日志处理这种场景下是完全OK的,所以像Kafka这类的系统利用这一特点减少确认从而大大提高了并发。级别0适合鸡肋数据场景,食之无味弃之可惜,就这么着吧。
mqtt协议有14种消息类型,如下 CONNECT:客户端连接到MQTT代理 CONNACK:连接确认 PUBLISH:新发布消息 PUBACK:新发布消息确认,是QoS 1给PUBLISH消息的回复 PUBREC:QoS 2消息流的第一部分,表示消息发布已记录 PUBREL:QoS 2消息流的第二部分,表示消息发布已释放 PUBCOMP:QoS 2消息流的第三部分,表示消息发布完成 SUBSCRIBE:客户端订阅某个主题 SUBACK:对于SUBSCRIBE消息的确认 UNSUBSCRIBE:客户端终止订阅的消息 UNSUBACK:对于UNSUBSCRIBE消息的确认 PINGREQ:心跳 PINGRESP:确认心跳 DISCONNECT:客户端终止连接前优雅地通知MQTT代理
现在我们深入到mqtt协议的细节,探究一下它是如何建立在tcp协议之上进行消息通信的。
我们知道http协议有不同的请求类型,如:GET、POST、PUT、DELETE
一条典型的get请求的报文格式如下: GET /index.html HTTP/1.1
mqtt协议的报文格式,由以下三部分构成: [ Fixed Header | Variable Header | Payload]
**Fixed Header: ** 固定头部,MQTT协议分很多种类型,如连接,发布,订阅,心跳等。其中固定头是必须的,所有类型的MQTT协议中,都必须包含固定头。
固定头包含两部分内容,首字节(字节1)和剩余消息报文长度(1-4字节)。
首字节用于表示MQTT消息的报文类型以及某些类型的控制标记,如图。高4位(bit7~bit4)表示协议类型,总共可以表示16种协议类型,其中0000和1111是保留字段。
报文类型字段值数据方向描述保留0禁用保留CONNECT1Client ---> Server客户端连接到服务器CONNACK2Server ---> Client连接确认PUBLISH3Client <--> Server发布消息PUBACK4Client <--> Server发不确认PUBREC5Client <--> Server消息已接收(QoS2第一阶段)PUBREL6Client <--> Server消息释放(QoS2第二阶段)PUBCOMP7Client <--> Server发布结束(QoS2第三阶段)SUBSCRIBE8Client ---> Server客户端订阅请求SUBACK9Server ---> Client服务端订阅确认UNSUBACRIBE10Client ---> Server客户端取消订阅UNSUBACK11Server ---> Client服务端取消订阅确认PINGREQ12Client ---> Server客户端发送心跳PINGRESP13Server ---> Client服务端回复心跳DISCONNECT14Client ---> Server客户端断开连接请求保留15禁用保留首字节的低4位(bit3~bit0)用来表示某些报文类型的控制字段,实际上只有少数报文类型有控制位,如下图。
报文类型固定头标记Bit 3Bit 2Bit 1Bit 0CONNECT保留0000CONNACK保留0000PUBLISHUsed in MQTT 3.1.1DUPQoSQoSRETAINPUBACK保留0000PUBREC保留0000PUBREL保留0010PUBCOMP保留0000SUBSCRIBE保留0010SUBACK保留0000UNSUBACRIBE保留0010UNSUBACK保留0000PINGREQ保留0000PINGRESP保留0000DISCONNECT保留0000当发布PUBLISH消息时,如果DUP字段(bit 3)设置为1,表明这是一条重复消息,否则是第一次发布消息。为了保证消息的可靠性传递,当QoS设置为1时,客户端或服务器发布消息时,需要得到对方的确认(PUBACK),如果一段时间后没收到PUBACK,那么会再次发送当前消息,并将DUP字段标记为1。
QoS用来表明QoS等级,如果Bit 1和Bit 2都为0,表示QoS 0。如果Bit 1为1,表示QoS 1。如果Bit 2为1,表示QoS 2。如果同时将Bit 1和Bit 2都设置成1,那么客户端或服务器认为这是一条非法的消息,会关闭当前连接。
目前Bit[3-0]只在PUBLISH协议中使用有效,并且表中指明了是MQTT 3.1.1版本。对于其它MQTT协议版本,内容可能不同。所有固定头标记为"保留"的协议类型,Bit[3-0]必须保持与表中保持一致,如SUBSCRIBE协议,其Bit 1必须为1。如果接收方接收到非法的消息,会强行关闭当前连接。
剩余消息报文长度(Remaining Length),即Variable Header + Payload的长度。剩余长度从Byte 2开始,最长可达4字节。所以剩余长度范围是Byte[2-5]。
**Variable Header:**可变头部,可变头部不是可选的意思,而是指这部分在有些协议类型中存在,在有些协议中不存在。
**Payload:**消息载体,就是消息内容。与可变头一样,在有些协议类型中有消息内容,有些协议类型中没有消息内容。
下面是一条具体的mqtt消息的16进制数据字节码:
32 14 00 06 74 6f 70 69 63 31 00 01 48 65 6c 6c 6f 20 4d 51 54 54首字节 0x32=0011 0010,对照首字节中的表,4位高字节为0011=3,表示PUBLISH,4位低字节0010,分别表示DUP 0,QoS 1(占两位),Retain 0。
Remaining Length 0x14=20,表示剩余消息长度为20。
PUBLISH (QoS>0)报文消息包含可变头部,其可变头部包含topic name和Packet Identifier。其格式为:
00 06表示topic name的长度,所以topic name长度是6
74 6f 70 69 63 31表示topic name的UTF8字符串,其值为"topic1"。
00 01是Packet Identifier,所以消息ID为1。
48 65 6c 6c 6f 20 4d 51 54 54是Payload,表示“Hello MQTT"的UTF8字节码。