【物联网】MQTT协议

维基 和 官网 的 文档资料比较完整,引用一张网络上的关于解释MQTT协议工作原理的图片:

                    

简单的说,MQTT就是一种基于发布-订阅结构的(publish-subscribe-based messaging protocol)协议,其实现基于TCP/IP协议,也可以使用UDP等协议实现。

消息的生产者产生新的消息的过程可以称为“发布”,消息的消费者想要获得消息的过程称为“订阅”,发布消息和订阅消息都伴随着一个消息的种类,也就是消息的主题Topic,发布消息和订阅消息都需要指定消息的Topic(好比一群人聚会,人们按照自己的兴趣爱好组成一支支小团体,而我对打篮球感兴趣,所以我参与到了讨论篮球的团体中去了,这样我就能收到这个篮球团体中的所有信息)。一个生产者可以生产多种主题的消息,同样的,一个消费者可以对多种主题的消息感兴趣。在MQTT协议中,消息的生产者消费者是处于平等的关系,一个生产者可以同时是一个消费者(卖肉的也得买素菜吃),但是为了“规范市场”,不能要生产者和消费者之间乱交易,因此,他们之间进行“交易”需要一个第三方机构介入,这个机构就是“工商局”Broker,Broker的作用就是在生产者产生某主题(Topic)的消息时,将该消息转发至对该主题感兴趣的消费者。

基于上面的说法,我们可以使用MQTT协议实现终端设备和服务器(这个服务器不是Broker服务器,而是应用相关的服务器)之间的通讯。举个例子,有种共享单车叫做“小红”,每辆“小红”都使用NB-IOT网络连入到互联网,并使用MQTT协议连入到一个MQTT Broker 服务器。同时共享单车的后台管理程序(服务器)也会连接到同一个MQTT Broker 服务器。每个“小红”都可以发布一个Topic为 “/SharedBicycle/xiaohong/endpoint”的消息,然后服务器会订阅这个主题,这样“小红”的信息(是否关锁、GPS位置、电池电量、信号强度等等)都可以发送给服务器了。同时,服务器会发布一个Topic为“/SharedBicycle/xiaohong/server-xxxxxxxx”的主题(其中“xxxxxxxx”表示单车的唯一ID号,但是“小红”发布的Topic中没有包含ID,因为ID信息可以直接包含在数据包字段中,这样可以避免每辆“小红”都需要发布一个专门的Topic,因此可以减小Broker服务器的负担),每个“小红”都会订阅和自己ID对应的主题,这样服务器就可以发送控制命令给某个“小红”,实现开锁或者状态查询功能。(上诉方案只是为了解释MQTT工作的一个简单设想,很多细节没有考虑,例如传输加密,Broker性能等问题)

MQTT Control Packet帧是MQTT协议中的基本传输单元,所有连接、断开、发布、订阅等操作都是通过发送MQTT Control Packet帧实现的。MQTT Control Packet帧结构如下(摘自官方Doc):

Figure 2.1 – Structure of an MQTT Control Packet

Fixed header, present in all MQTT Control Packets

Variable header, present in some MQTT Control Packets

Payload, present in some MQTT Control Packets

Figure 2.2 - Fixed header format

Bit

7

6

5

4

3

2

1

0

byte 1

MQTT Control Packet type

Flags specific to each MQTT Control Packet type

byte 2…

Remaining Length

 Fixed header format中,byte 1用于表示帧类型以及标志位,byte 2是用于表示后续的数据长度,表示为了更高效的表示数据长度,MQTT采用的计数方式和传统的计数方式不同:

1、先假设后续数据的实际长度为L(不包含Remaining Length部分),Remaining Length部分的字节数最多为4个字节。

2、使用byte 2的低7位用于表示 L 的低7位(最多只能表示 2^7=128 字节),如果 L 的值小于等于127,那么一个字节足够表示 L 的值。如果 L 的值大于127,那么一个字节不够表示 L 的值,置位byte 2的最高位,表示需要后续字节表示数据长度。

3、L = L / 128。我们将这种结构看成是一种 128进制 的计数方式,第一个字节是数据的第一位,第二个字节是数据的第二位,那么 L % 128 得到的就是数据的第一位的值,L / 128 得到的就是数据右移一位(128进制数右移一位相当于二进制右移7位)之后的数据,这时候再运算 L % 128 得到的就是第二位数的值。

4、通过上面的方法计算出以128进制表示的 L 的4个位的数值,分别对应到Remaining Length的最多四个字节,为了提高数据使用率,从高位开始省略为0的位。

计算方法可以总结出来了:

1、用128进制的表示 L 为 ABCD,A为最高位,那么 L = (A * 128^3) + (B * 128^2) + (C * 128^1) + (D * 128^0) 。 

2、反过来,D = L / 128^0 % 128

                      C = L / 128^1 % 128

                      B = L / 128^2 % 128

                      A = L / 128^3 % 128

举例如下:

1、L = 65,ABCD = (0)(0)(0)(65),对应的Remaining Length字节分别为 (0x41)。

2、L = 321,ABCD = (0)(0)(2)(65),对应的Remaining Length字节分别为(0x02) (0x41 | 0x80)。

3、L = 123456,ABCD = (0)(7)(68)(64),对应的Remaining Length字节分别为(0x07) (0x44 | 0x80) (0x7F | 0x80)。

4、L = 268435455,ABCD = (127)(127)(127)(127),对应的Remaining Length字节分别为(0x7F) (0xFF) (0xFF) (0xFF)。

上面示例中 “ | 0x80 ”操作是为了表示需要下一个字节表示长度,但是第四个字节的最高位是不能置位的,因为Remaining Length字段最多只能有4个字节来表示。

MQTT Control Packet中的 type 字段表示的是帧类型,取值如下:

不同的Control Packets包含的Variable header和Payload格式也不一样,具体格式参考官方文档:http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718008

以CONNECT帧为例, 下面是CONNECT的帧格式:

Fixed header

Variable header

Payload

Protocol Name

Protocol Level

Connect Flags

Keep Alive

根据CONNECT的帧格式创建一个CONNECT的帧:

1、Fixed header字段中“10”表示帧类型为CONNECT帧,“16”为Remaining Length,表示后面还有22字节的数据。

2、Variable header的Protocol Name字段中,“00 04”表示协议名长度为4个字节:“MQTT”。

3、Variable header的Protocol Level字段表示协议版本,这里Protocol Level取值为04,表示MQTT版本为version 3.1.1。

4、Variable header的Connect Flags字段表示了协议的User Name、Password、Wiil Retain、Will QoS、Will Flag、Clean Session信息。这些标志位代表的内容这里不一一解释了。这里Connect Flags取值为02。

5、Variable header的Keep Alive字段是一个16位数据,表示的是间隔时间,计数单位为second。

The Keep Alive is a time interval measured in seconds. Expressed as a 16-bit word, it is the maximum time interval that is permitted to elapse between the point at which the Client finishes transmitting one Control Packet and the point it starts sending the next. It is the responsibility of the Client to ensure that the interval between Control Packets being sent does not exceed the Keep Alive value. In the absence of sending any other Control Packets, the Client MUST send a PINGREQ Packet [MQTT-3.1.2-23].

 The Client can send PINGREQ at any time, irrespective of the Keep Alive value, and use the PINGRESP to determine that the network and the Server are working.

 If the Keep Alive value is non-zero and the Server does not receive a Control Packet from the Client within one and a half times the Keep Alive time period, it MUST disconnect the Network Connection to the Client as if the network had failed [MQTT-3.1.2-24].

 If a Client does not receive a PINGRESP Packet within a reasonable amount of time after it has sent a PINGREQ, it SHOULD close the Network Connection to the Server.

 A Keep Alive value of zero (0) has the effect of turning off the keep alive mechanism. This means that, in this case, the Server is not required to disconnect the Client on the grounds of inactivity.
Note that a Server is permitted to disconnect a Client that it determines to be inactive or non-responsive at any time, regardless of the Keep Alive value provided by that Client.

6、Payload字段是有效数据载荷,分为若干个字段,并且至少有一个字段 Client Identifier,其表示客户端的标识符,或者说为客户端的“名字”,这个 Client Identifier为UTF-8的编码格式。剩下的字段内容依据Connect Flags的值而定,以这里Connect Flags的值为“02”为例,User Name、Password、Wiil Retain、Will Flag都为0,表示不需要这些信息,那么在Payload中就不需要有关于这些Flag的内容,Clean Session的值为1,“If CleanSession is set to 1, the Client and Server MUST discard any previous Session and start a new one. This Session lasts as long as the Network Connection. State data associated with this Session MUST NOT be reused in any subsequent Session”,表示不存放之前的Session内容。那么也就是说Payload中除了 Client Identifier 字段不需要其他内容,这里我们放置的内容为:“00 0A 43 6C 69 65 6E 74 54 65 73 74”,这是UTF-8编码格式,最前面的两个字节表示的是字符串的长度,后面的是有效数据,转成ASCII码为:“ClientTest”。

现在我们得到了一帧数据:“10 16 00 04 4D 51 54 54 04 02 00 3C 00 0A 43 6C 69 65 6E 74 54 65 73 74 ”,使用网络调试助手进行测试,创建一个TCP客户端连接到一个开放的MQTT Broker(不需要用户名和密码),然后发送上面的帧,结果如下:

发送完之后收到了一帧数据{20 02 00 00},就是一个CONNACK帧,表示连接到Broker成功。

Mosquitto是一款开源的MQTT Broker服务器,安装使用都很简单,官网下载Windows版本的安装包即可安装,安装完成后如果没有运行Mosquitto,可以在安装目录下手动打开mosquitto.exe程序,这时候就开启了MQTT broker服务器了,由于我们是在内网环境下运行的Mosquitto程序,其它网络设备并不能访问该Broker,我这里使用的了一个端口映射工具:http://www.youtusoft.com,将本机MQTT broker的1883端口映射到公网的一个端口:

这样我的内网中的电脑就可以当一个服务器使用了,但是免费版的只能提供一路免费的端口映射服务。通过外网IP和端口号连接到我的电脑上的Mosquitto之后,可以正常使用。经过测试,发现Mosquitto服务器会关闭“3分钟内没有交互消息的设备”的连接,这个时间长度应该是可以设置的,在Mosquitto的安装目录下有一个mosquitto.conf文件,这个文件就是Broker的配置文件,但是具体修改配置文件中的哪个参数还没有研究。我们将MQTT终端设备的KeepAlive的值设置成60之后就可以避免设备被主动关闭了,但是即使是这样我们依然需要有重连的功能,因为Broker服务器有可能崩溃,所以无论如果都需要可靠的重连接机制。

 

其它MQTT工具及参考链接:

MQTT Client测试工具MQTTfx:http://mqttfx.jensd.de/index.php/download

免费MQTT Broker站点:http://www.tongxinmao.com/txm/webmqtt.php#

其它开放的MQTT Broker站点:

Server Broker Port Websocket
iot.eclipse.org Mosquitto 1883 / 8883 n/a
broker.hivemq.com HiveMQ 1883 8000
test.mosquitto.org Mosquitto 1883 / 8883 / 8884 8080 / 8081
test.mosca.io mosca 1883 80
broker.mqttdashboard.com HiveMQ 1883

在MQTT服务器上使用TLS进行安全通信:https://baijiahao.baidu.com/s?id=1610669918084024139&wfr=spider&for=pc

 

猜你喜欢

转载自blog.csdn.net/tq384998430/article/details/101287875