在MQTT协议中,一个控制报文(数据包)的结构按照前后顺序分如下三部分:

结构名 中文名 解释说明
Fixed header 固定报头 报文的最开始部分,所有报文都包含这个部分
Variable header 可变报头 固定报文的附加部分,有些报文没有这个部分
Payload 有效载荷 需要携带的信息内容,有些报文没有这个部分

  下图是MQTT控制报文(数据包)格式的结构示意图:

1、固定报头(Fixed header):

  固定报头存在于所有MQTT数据包中,表示数据包类型及控制类标志等。固定报头由至少2个字节组成,格式如下:

Bit(位号) 7 6 5 4 3 2 1 0
Byte1(第一个字节) 组合代表MQTT控制报文(数据包)的类型 控制报文的标志位(Flags),可理解为一种属性参数
Byte2(第二个字节起) 剩余长度,当前报文剩余部分的字节数,包括可变报头和有效负载

1.1、控制报文类型(Control Packet type):

  固定报头第一个字节的高四位(7-4号位)是代表控制报文的类型,也就是这个数据包是做什么用的。是用7-4号位的二进制(也就是1111--0000之间)组合值,来代表具体的含义,见下表:

7-4号位 十进制值 报文类型 报文允许发起方向 报文描述
0000 0 Reserved 禁止 保留,不可用
0001 1 CONNECT 客户端―→服务端 客户端请求连接到服务端的代理服务
0010 2 CONNACK 客户端←―服务端 连接请求的回复确认报文
0011 3 PUBLISH 客户端←→服务端 发布主题消息
0100 4 PUBACK 客户端←→服务端 发布确认,是QoS=1时,对 PUBLISH 的响应确认
0101 5 PUBREC 客户端←→服务端 发布收到,是QoS=2时,对 PUBLISH 的响应确认,是QoS=2实现的第一步
0110 6 PUBREL 客户端←→服务端 发布释放,是QoS=2时,对 PUBREC 的响应确认,是QoS=2实现的第二步
0111 7 PUBCOMP 客户端←→服务端 发布完成,是QoS=2时,对 PUBREL 的响应确认,是QoS=2实现的第三步
1000 8 SUBSCRIBE 客户端―→服务端 客户端订阅主题,可一次订阅一个或多个主题(使用通配符)
1001 9 SUBACK 客户端←―服务端 订阅完成确认,是对 SUBSCRIBE 的响应确认
1010 10 UNSUBSCRIBE 客户端―→服务端 取消订阅,客户端发起的取消对某个主题的订阅
1011 11 UNSUBACK 客户端←―服务端 取消订阅确认,是对 UNSUBSCRIBE 的响应确认
1100 12 PINGREQ 客户端―→服务端 心跳,表示这个数据包是为通知服务端客户端还在正常连接着
1101 13 PINGRESP 客户端←―服务端 心跳响应,表示服务端已经成功收到了客户端的心跳
1110 14 DISCONNECT 客户端―→服务端 断开连接,客户端通知服务端,需要断开当前网络连接
1111 15 Reserved 禁止 保留,不可用

1.2、标志(Flags):

  固定报头第1个字节的低4位 (3-0号位)包含每个MQTT控制报文类型特定的标志,必须与控制报文类型配套对应使用,否则服务端代理服务会拒绝服务或断开连接。具体的见下表(保留的标志必须按照表中的值设置):

报文类型 标志类型 Bit3 Bit2 Bit1 Bit0
CONNECT 保留 0 0 0 0
CONNACK 保留 0 0 0 0
PUBLISH 使用 是否为重复发 服务质量高位 服务质量低位 是否保存消息
PUBACK 保留 0 0 0 0
PUBREC 保留 0 0 0 0
PUBREL 保留 0 0 1 0
PUBCOMP 保留 0 0 0 0
SUBSCRIBE 保留 0 0 1 0
SUBACK 保留 0 0 0 0
UNSUBSCRIBE 保留 0 0 1 0
UNSUBACK 保留 0 0 0 0
PINGREQ 保留 0 0 0 0
PINGRESP 保留 0 0 0 0
DISCONNECT 保留 0 0 0 0

  注:关于用“是否”描述的实际就是布尔类型,0表示否,1表示是;

1.3、第一字节各类型报文具体值:

  固定报头报文类型高4位和标志位的低4位综合起来,最终第一个字节是有一个具体值的。为了更好的理解第一个字节的具体值是怎样得出来的,在下表列出了不同类型的报文及某个报文不同标志时的具体值:

报文类型 标志作用 二进制值 10进制值 16进制值
CONNECT 连接服务端 00010000 16 0x10
CONNACK 连接成功确认 00100000 32 0x20
PUBLISH 新发布等级0不保存 00110000 48 0x30
PUBLISH 新发布等级0需保存 00110001 49 0x31
PUBLISH 新发布等级1不保存 00110010 50 0x32
PUBLISH 新发布等级1需保存 00110011 51 0x33
PUBLISH 新发布等级2不保存 00110100 52 0x34
PUBLISH 新发布等级2需保存 00110001 53 0x35
PUBLISH 重发等级2不保存 00111000 56 0x38
PUBLISH 重发等级2需保存 00111001 57 0x39
PUBACK 等级1发布成功 01000000 64 0x40
PUBREC 等级2发布收到 01010000 80 0x50
PUBREL 等级2发布释放 01100010 98 0x62
PUBCOMP 等级2发布完成 01110000 112 0x70
SUBSCRIBE 订阅主题 10000010 130 0x82
SUBACK 订阅完成确认 10010000 144 0x90
UNSUBSCRIBE 取消订阅 10100010 162 0xA2
UNSUBACK 取消完成确认 10110000 176 0xB0
PINGREQ 心跳包 11000000 192 0xC0
PINGRESP 心跳回复 11010000 208 0xD0
DISCONNECT 断开网络连接 11100000 224 0xE0

  注:关于发布主题还有其他情况这里就没有全部列出,根据表中的规律就可以计算出实际的值了。

1.3、剩余长度(Remaining Length):

  剩余长度是从第二个字节开始,最多允许占用四个字节。描述本次传送的应用消息在剩余长度字节之后(不包括剩余长度字节本身)还有多少个字节,包括可变报头(有的报文没有这部分) + 有效载荷(有的报文没有这部分)的所有字节数量。

  根据上面描述,剩余长度属于变长的编码规则,也就是它可能是1-4个字节中的任何一种情况,那么怎样知道当前这个报文的剩余长度是占用了几个字节的呢?如果不能确定,那么接收方就无法正确解析数据了。所以MQTT协议规定剩余长度的每个字节的最高位(也就是7号位)作为是否还有下一个字节剩余长度的标志位,不做长度数值的表述位。这样每给剩余长度字节最大代表长度值就是127(二进制 1111111 的值)了,因为只有7个位表示长度了。向后每增加一个字节都代表前一个字节满值再加1的倍数,四个字节的剩余长度代表的长度值最大可为268435455。

  如果剩余长度值不大于127,则只用一个字节表示,例如121,则剩余长度字节的二进制是01111001,含义见下表:

7号位 6-0号位
0 1111001
接下来没有剩余长度字节了 剩余长度是:121

  如果剩余长度值大于127小于16384,则需用两个字节表示,例如15971,则剩余长度两字节具体值则是0xE3 0x7C(11100011 01111100),含义见下表:

1字节7号位 1字节6-0号位 2字节7号位 2字节6-0号位
1 1100011 0 1111100
后面还有字节描述长度 本子节描述长度:99 后面没有长度字节了 本字节描述长度:124 * 128 = 15872

  两个字节代表的长度值相加 99 + 15872 = 15971,这既是完整的剩余长度值了。后面这个字节每增加1,则代表剩余长度值增加128。也就是前面字节的低7位值满都为1(127)再加1,就到后面字节加1,前面字节低7位归0。再加满再到后面字节加1,以此类推。所以两个字节可以表述的最大值是(11111111 01111111)127+(127*128) = 16383。
  由于使用了两个字节表述剩余长度,那么前面的字节的最高位7号位就要置1,以告诉解析程序后面的字节还要按照剩余长度来计算。

  如果剩余长度值大于16383小于2097152,则需用三个字节表示,例如2097150,则剩余长度三字节具体值则是0xFE 0xFF 0x7F(11111110 11111111 01111111),含义见下表:

1字节7号位 1字节6-0号位 2字节7号位 2字节6-0号位 3字节7号位 3字节6-0号位
1 1111110 1 1111111 0 1111111
还有长度字节 长度:126 还有长度字节 长度:127 * 128 = 16256 长度最后字节 长度:127 * 16384 = 2080768

  三个字节代表的长度值相加 126 + 16256 + 2080768 = 2097150,这既是完整的剩余长度值了。3字节每增加1,则代表剩余长度值增加16384,即前两个字节满值再加1。四字节的原理也是这样向后推导,这里就不再列举了。

  剩余长度使用1-4个字节可以描述的长度范围见下表:

字节数 最小值10/16进制 最小值2进制 最大值10/16进制 最大值2进制
1 0(0x00) 00000000 127(0x7F) 01111111
2 128(0x80,0x01) 10000000 00000001 16383(0xFF,0x7F) 11111111 01111111
3 16384(0x80,0x80,0x01) 10000000 10000000 00000001 2097151(0xFF,0xFF,0x7F) 11111111 11111111 01111111
4 2097152(0x80,0x80,0x80,0x01) 10000000 10000000 10000000 00000001 268435455(0xFF,0xFF,0xFF,0x7F) 11111111 11111111 11111111 01111111

2、可变报头(Variable header):

  可变报头在固定报头与有效负载之间,不是所有的报文都有可变报头。报文类型不同可变报头的内容也不同。后面会对各报文的可变报头逐一讨论。某些类型的报文中的可变报头还包含报文标识符(Packet Identifier)字段。

2.1、报文标识符(Packet Identifier):

  报文标识符,一定程度上相当于是每个报文的唯一ID,用于识别报文身份的。重复发送报文时,必须使用相同的报文标识符。在需要应答的控制报文里,标识符可以区分是应答的哪个报文。某些控制报文的可变报头部分包含一个两字节的报文标识符字段。这些报文分别是PUBLISH(QoS > 0时), PUBACK,PUBREC,PUBREL,PUBCOMP,SUBSCRIBE, SUBACK,UNSUBSCRIBE,UNSUBACK。
  需要使用标识符的报文,发送方在每次发送一个新的报文时,必须分配一个没有使用过的报文标识符。报文标识符固定使用两个字节,按照双字节读值可用范围是0-65535(00000000 00000000 -- 11111111 11111111)。

3、有效载荷(Payload):

在一些需要携带用户自定义的应用消息的MQTT控制报文中,会将这些信息放在报文的最后部分,称之为有效载荷。对于PUBLISH来说有效载荷就是应用消息。不同的控制报文有效载荷内容不同,后面会在分别介绍控制报文时具体讨论。下表列出哪些控制报文有包含有效载荷:

控制报文 有效载荷
CONNECT 需要
CONNACK 不 需要
PUBLISH 可选,可以零长度
PUBACK 不需要
PUBREC 不需要
PUBREL 不需要
PUBCOMP 不需要
SUBSCRIBE 需要
SUBACK 需要
UNSUBSCRIBE 需要
UNSUBACK 不需要
PINGREQ 不需要
PINGRESP 不需要
DISCONNECT 不需要

  本节完,待续......

转战物联网·基础篇07-深入理解MQTT协议之控制报文(数据包)格式的更多相关文章

  1. 转战物联网·基础篇05-通俗理解MQTT协议的实现原理和异步方式

      网络上搜索MQTT协议,会出现太多的解释,这里就不做官方标准释义的复制了.这一节我们从实战理解角度,通俗的将MQTT协议的作用及实现原理说一下,旨在可以快速理解MQTT协议.所以可能会出现很多看似 ...

  2. 转战物联网·基础篇06-深入理解MQTT协议之基本术语

      通过上一节我们对MQTT协议已经有了初步的印象,这一节我们开始深入的理解一下MQTT协议,介绍常用的MQTT 3.1.1版本,5.0版本后面指介绍新增部分即可.这一节我们先介绍MQTT里常用的术语 ...

  3. 转战物联网·基础篇08-例说MQTT协议各控制报文

      前面讨论了MQTT协议的控制报文的格式,下面分别举例探讨各个控制报文的详细内容. 01.CONNECT – 连接服务端   客户端到服务端的网络连接建立后,客户端发送给服务端的第一个报文必须是CO ...

  4. 转战物联网·基础篇09-选择MQTT协议还是CoAP协议

      前面章节介绍过,MQTT协议和CoAP协议都是物联网中比较流行的协议,都对传输量做了很大的精简,传输开销小,以适应物理网的网络环境.   XMPP协议也有人说是适合物联网通信的,但它是基于XML, ...

  5. 转战物联网·基础篇03-从JSON数据到短指令谈思维的转变

      了解了物联网项目的大体结构之后,我们先从物联网的联网相关部分说起,这也是物联网项目中的关键环节.在联网环节中,不仅要考虑如何连接上,还要考虑连接后如何传输数据.换句话说数据是以什么格式进行传输,对 ...

  6. iOS系列 基础篇 07 Action动作和输出口

    iOS系列 基础篇 07 Action动作和输出口 目录:  1. 前言及案例说明 2. 什么是动作? 3. 什么是输出口? 4. 实战 5. 结尾 1. 前言及案例说明 上篇内容我们学习了标签和按钮 ...

  7. 网络基础之网络协议篇---CS架构--网络通信--osi 协议---套接字socket--粘包

    1 C\S 客户端/服务器架构: .硬件 C/S架构 (打印机) .软件 C/S 架构 互联网中处处是C/S架构 如黄色网站是服务端,你的浏览器是客户端(B/S架构也是C/S架构的一种) 腾讯作为服务 ...

  8. 物联网防火墙himqtt源码之MQTT协议分析

    物联网防火墙himqtt源码之MQTT协议分析 himqtt是首款完整源码的高性能MQTT物联网防火墙 - MQTT Application FireWall,C语言编写,采用epoll模式支持数十万 ...

  9. Java多线程系列--“基础篇”07之 线程休眠

    概要 本章,会对Thread中sleep()方法进行介绍.涉及到的内容包括:1. sleep()介绍2. sleep()示例3. sleep() 与 wait()的比较 转载请注明出处:http:// ...

随机推荐

  1. CODING 敏捷看板全新上线,助力研发管理可视化升级

    在服务企业研发团队的过程中,我们发现不少团队碰到了类似的问题: 团队成员声称完成了自己的大部分任务,但团队实际交付的需求却寥寥无几? 由于某些问题导致工序一直处于等待状态,怎么识别和处理这些延迟? 成 ...

  2. 局域网地址为什么是192.168.X.X?为什么连上公司的VPN就上不了网?

    注:本文主要目的是给程序员讲述一些局域网/VPN的基本知识,并不涉及到具体的实操.关于如何安装VPN服务器.配置VPN客户端及修改Windows路由表等具体实操内容,请自行搜索. RFC 局域网地址为 ...

  3. C++之typename和typedef关键字

    1. typename的作用 template <typename Distance> class KDTreeIndex : public NNIndex<Distance> ...

  4. Promise代码详解(show you the code)

    认识异步函数 同步函数: const sum1 =(a,b)=>{ return a+b } console.log('AAA'); console.log(sum(5,6)); conosle ...

  5. NLP标记集资料

    title: 标记集 tags: grammar_cjkRuby: true --- LTP分词标注集 标记 含义 举例 B 词首 __中__国 I 词中 哈__工__大 E 词尾 科__学__ S ...

  6. C# 使用.net core 驱动树莓派的IO信号

    如何使用.net core来驱动树莓派的IO信号?是我们的实际项目需求中,可能就会有这种小项目,我们要输出一个IO信号,此处我们拿了树莓派4做测试 一共有两排引脚,引脚的顺序定义及功能如下: 我们就参 ...

  7. MongoDB(三):数据库操作、集合操作

    1. 数据库操作 看完前面的文章,大家应该把环境搭建好了,下面我们就开始学习MongoDB的一些基本操作了. 首先我们要了解的一些要点: MongoDB将数据存储为一个文档,数据结构由键值对(key= ...

  8. Vue学习笔记Day2

    1.mustache语法 如何将data中的文本数据插入到HTML中? 通过使用mustache语法(也就是双大括号),将data中的变量名插入到HTML元素中,显示在页面上. 如下图:并且数据是响应 ...

  9. Html5 小游戏 俄罗斯方块

    导言 在一个风和日丽的一天,看完了疯狂HTML 5+CSS 3+JavaScript讲义,跟着做了书里最后一章的俄罗斯方块小游戏,并做了一些改进,作为自己前端学习的第一站. 游戏效果: 制作思路 因为 ...

  10. 【Postman】举例实战——天气查询

    准备工作: 1.下载postman(搜索官网下载即可) 2.接口文档(以聚合上去免费API:天气查询接口) 打开postman: 1.输入url 2.请求方式 3.请求参数 4.在postman中运行 ...