文章目录
本文主要介绍MQTT协议的结构和具体的2条报文数据解析,帮忙更简单、快速地理解mqtt协议,如果要深入了解实现完整的协议,可以查看文章最后的完整协议文档做更深入的研究。
MQTT协议在lot领域是使用的最广泛的通用协议,在一般企业级物联网产品开发中,通常会考虑的协议基本上就只有2种,一种是私有的自定义协议,另一种就是通用的MQTT协议;在产品品类相对较少或者对系统性能要求极高的情况下,一般会选择自定义的协议,其他大部分情况下,都会选择MQTT协议。
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的“轻量级”通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。
mqtt协议的优点,官方的解释是:用极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务;简单理解就是,实现起来简单,并且在传输上无效的数据也很少,而且能够保证数据传输的可靠性。
MQTT协议由三部分组成,固定报头,可变报头,有效载荷;固定报头是所有的报文统一的格式,可变抱头则根据固定抱头中的报文类型不同基本不同,每个报文类型基本上都有自己的可变报头格式,这里需要注意,最后有效载荷则部分报文有,部分报文没有,而且报文内容也是根据报文类型的不同而不同;协议总结构如下图所示:

MQTT协议总体来讲,就是规定了16条报文的具体规范,每一条报文都有自己的用途,自己的规定;一开始接触MQTT协议的时候,因为概念和名字较多可能会有点不容易理解,但是梳理清楚一两条具体报文的含义之后,就能比较清晰的理解这份协议了;把16条报文理解清楚,就能实现完整的MQTT协议。
固定报头是MQTT协议开头,2个字节,分为三个部分:标志位、报文类型、剩余长度,下图为固定报头结构示意图

在上面的结构示意图中可以看的比较清晰;第一个字节分为2个部分:标志位和报文类型,一个字节有8位二进制,前面的4位用来做标志位,标志位在每条报文中有不同的作用,具体作用看后面的单条报文数据分析
后4位用来表示表示具体的报文类型,一共有16种,有部分类型是预留未使用的类型,在代码实现时,可以暂时忽略;具体的报文类型如下表所示:

第二个字节是剩余长度,其表示的就是在剩余长度之后的数据长度(还有多少个字节),剩余长度的字节数是不固定的,至少一个字节,最多4个字节,所以固定报头中包含的一个字节就是剩余长度的第一个字节;这里需要说明一下,剩余长度的每一个字节的最高位是一个标志位,用来表示下一个字节是否也属于剩余长度
举例说明:
比如:
0100 0000(最高位为0) 则说明后面的字节数据不属于剩余长度了
1010 0000(第一个字节,最高位为1) 0000 0001(第二个字节,最高位为0) 则说明后面的字节数据属于剩余长度,第2个字节最高位为0,则说明后面没有属于剩余长度的字节了,那么此剩余长度则为这2个字节
因为剩余长度的每个字节的最高位为标志位,所以其真实值并不是这些字节直接合并而成,需要另外进行计算;
举例说明:
如果第一个字节的最高位为0,则说明剩余长度只有一个字节,并且其值就是这个字节的值,如:0100 0000 那么剩余长度的真实值就是:64
如果剩余长度超过一个字节,那么就需要将每个字节的高字节去掉,然后组成一个新的数据,计算其值,下面举例说明:
2个字节长度的剩余长度计算
| 第一个字节 | 第二个字节 |
|---|---|
| 1010 0000 | 0000 0001 |
去掉标志位后,其新值为
| 第一个字节 | 第二个字节 |
|---|---|
| 010 0000 | 000 0001 |
合并时,后面的字节在高位,则合并后值为:1010 0000,则剩余长度的值为十进制的160;
三个字节长度的剩余长度计算
| 第一个字节 | 第二个字节 | 第三个字节 |
|---|---|---|
| 1001 0001 | 1100 1001 | 0100 1001 |
去掉标志位后,其新值为
| 第一个字节 | 第二个字节 | 第三个字节 |
|---|---|---|
| 001 0001 | 100 1001 | 100 1001 |
后面的字节在高位,则合并后值为:100 1001 100 1001 001 0001,则剩余长度的值为十进制的1205393;
四个字节长度的剩余长度计算
| 第一个字节 | 第二个字节 | 第三个字节 | 第四个个字节 |
|---|---|---|---|
| 1001 0001 | 1100 1001 | 1100 1001 | 0100 1101 |
去掉标志位后,其新值为
| 第一个字节 | 第二个字节 | 第三个字节 | 第四个个字节 |
|---|---|---|---|
| 001 0001 | 100 1001 | 100 1001 | 100 1101 |
后面的字节在高位,则合并后值为:100 1101 100 1001 100 1001 001 0001,则剩余长度的值为十进制的162686097;
剩余长度字段的解码算法如下:
multiplier = 1
value = 0
do
encodedByte = 'next byte from stream'
value += (encodedByte AND 127) * multiplier
multiplier *= 128
if (multiplier > 128*128*128)
throw Error(Malformed Remaining Length)
while ((encodedByte AND 128) != 0)
AND 是位操作与(C 语言中的&)
这个算法终止时,value 包含的就是剩余长度的值
可变报头的数据依据固定报头中的报文类型而不同,一般是包含和报文类型相关的数据;例如客户端连接服务器报文,包含了协议名,协议级别(用来表示mqtt的版本信息)、连接标志、保持连接;可变报头内容示意图如下:

发布消息报文可变报头,则内容仅包含了主题名和报文标识符(仅当标志位Qos>0)

有效载荷内容也是根据报文类型的不同而不同,在上述可变报头的2张示意图中也能看出,连接服务器报文的有效载荷则包含了:客户端标识符、遗属主题、遗属消息、用户、密码,而发布消息报文,则仅仅包含了应用消息;不同的有效载荷解析方式有所不同,具体要看每一条报文的规定,详情可以参见最后一个章节提供的Mqtt官方手册。
经过上面的学习,对MQTT协议应该有了一个初步的了解,接下来就使用2条具体的协议报文数据,更详细的介绍mqtt协议的具体数据和实现。
首先来看看连接服务器报文协议,先贴出具体的一条连接服务器报文协议数据(16进制数,每2个数字表示一个字节)
10ab0100044d51545404c2001400177061686f31363735313537353030373437303030303030000464656d6f00803846334238444532464443384244334437393242453737454143343132303130393731373635453542444436433439394144434545383430434534343142444546313745333036383442443935434137303846353530323232323243433631363144304432334332444643423132463841433939384635394537323133333933
接下来我们一个字节一个字节来解析,还是按照协议结构来分析(每一条报文所包含报文信息,在mqtt协议规范中都会有说明)

先把整体的解析数据罗列出,再进行详细的解析说明:

第1个字节是10,二进制则为:0001 0000(位置从后往前数) ,前4位为0000,则标志位全为0,标志位数据表示的意思需要根据报文类型来确定,详情可以参考文章最后的官方文档;后4位0001,值为1,则表示报文类型为客户端连接服务器(CONNECT)
第2个字节:ab,二进制为:1010 1011,第2个字节为剩余长度的起始字节,其最高位为1,则说明后面一个字节也属于剩余长度,第3个字节:01,二进制为:0000 0001 最高位为0,则剩余长度结束,共包含了2个字节,依据前面剩余长度的算法解释,可得出剩余长度等于171,表示该报文后面还有171个字节。
根据官方文档的解释:连接服务器报文可变报头的起始2个字节为协议名的长度;前面已经解析了3个字节;
则第4个字节至第5个字节则为协议名称的长度:00 04 协议名长度为4,
第6-9个字节为协议名,6-9字节为:4d 51 54 54 ,根据协议规定,协议名为ascii码值,则协议名解析为:MQTT
接下来的字节为协议级别,即第10个字节为协议级别:04 (官方解释:0x04 表示3.1.1版本)
接下来的第11个字节为 连接标志,连接标志位的详细解释如下图所示:

第11个字节:c2 二进制为:1100 0010,那么会话清理(clean session)为1,表示丢弃之前的会话,开始一个新的会话;另外用户名和密码标志位为1,则说明在有效载荷中携带了用户名和密码信息,遗属标志位为0,则有效载荷中就没有遗属主题和遗属消息
第12-13字节:00 14,用来表示保持连接的时间(秒),即为10十进制:20秒
到此处可变报头就解析完了,接下来就是解析有效载荷部分了,连接服务器报文有效载荷格式为前个字节为字段长度,然后接字段值的格式
第14-15字节:00 17,用来表示客户端标识符长度,即为10十进制:23个字节
第16-38字节:7061686f31363735313537353030373437303030303030 ,表示客户端标识符,客服端标识符为ascii码格式,则此处为:paho1675157500747000000
第39-40字节:00 04,用来表示用户长度,即为10十进制:4个字节
第41-44字节:64 65 6d 6f,用来表示用户名,ascii码为(demo)
第45-46字节:00 80 ,用来表示密码长度 ,即为10进制:128
第47-174字节:
3846334238444532464443384244334437393242453737454143343132303130393731373635453542444436433439394144434545383430434534343142444546313745333036383442443935434137303846353530323232323243433631363144304432334332444643423132463841433939384635394537323133333933
即为密码,也是使用ascii码
至此我们已经完整的解析了一条mqtt客户端连接服务器报文
下面来看发布消息报文,此报文可变报头仅有2个部分,一个是主题名,一个是报文标识符,报文标识符仅当Qos大于0时才有,然后就是有效载荷,有效载荷都是自己定义的内容,收到数据后按自定义规则解析即可;

301200047465737468656c6c6f2c776f726c64
先把整体的解析数据罗列出,再进行详细的解析说明:

第1个字节是30,二进制则为:0011 0000(位置从后往前数) ,前4位为0000,则标志位全为0,标志位数据表示的意思需要根据报文类型来确定,详情可以参考文章最后的官方文档;后4位0011 值为3,则表示报文类型为发布消息(PUBLISH)
第2个字节:11,二进制为:0001 0001,第2个字节为剩余长度的起始字节,其最高位为0,则说明剩余长度就只有这一个字节,10进制值为17,表示该报文后面还有17个字节。
第3个字节开始就是可变报头,发布消息类型的可变抱头,第一个就是主题,格式内容为2个字节的主题名称长度,然后跟上主题名;则第3-4个字节为主题名长度:00 04,表示有4个字节的主题名
第5-8字节为主题名:74657374 ,主题名为asicc码格式,则为:test
因为固定报头中标志位为0,则Qos=0,则没有报文标识符
第9-19字节为有效载荷:68656c6c6f2c776f726c64 (ascii码:hello,world)
再看一条带有Qos=1的发布消息报文数据
3213000474657374000168656c6c6f2c776f726c64
先列出整体的解析数据

这里解释一下什么是 QoS
很多时候,使用 MQTT 协议的设备都运行在网络受限的环境下,而只依靠底层的 TCP 传输协议,并不能完全保证消息的可靠到达。因此,MQTT 提供了 QoS 机制,其核心是设计了多种消息交互机制来提供不同的服务质量,来满足用户在各种场景下对消息可靠性的要求。
MQTT 定义了三个 QoS 等级,分别为:
QoS 0,最多交付一次。
QoS 1,至少交付一次。
QoS 2,只交付一次。
其中,使用 QoS 0 可能丢失消息,使用 QoS 1 可以保证收到消息,但消息可能重复,使用 QoS 2 可以保证消息既不丢失也不重复。QoS 等级从低到高,不仅意味着消息可靠性的提升,也意味着传输复杂程度的提升。在发布者到订阅者的消息投递流程中,QoS 等级是由发布者在 PUBLISH 报文中指定的。
第1个字节是32,二进制则为:0011 0010(位置从后往前数) ,第2-3位表示的是Qos,则Qos=1那么后面的可变报文中,就会添加报文标识符了;后4位0011 值为3,则表示报文类型为发布消息(PUBLISH)
第2个字节:13,二进制为:0001 0011,第2个字节为剩余长度的起始字节,其最高位为0,则说明剩余长度就只有这一个字节,10进制值为19,表示该报文后面还有19个字节。
第3个字节开始就是可变报头,发布消息类型的可变抱头,第一个就是主题,格式内容为2个字节的主题名称长度,然后跟上主题名;则第3-4个字节为主题名长度:00 04,表示有4个字节的主题名
第5-8字节为主题名:74657374 ,主题名为asicc码格式,则为:test
因为标志位中Qos=1,则下面的2个字节表示报文标识符,报文标识符一般都是用来表示消息数,用来区分不同的消息,避免处理重复消息
第9-10字节:00 01,则报文标识符为1
第11-21字节为有效载荷:68656c6c6f2c776f726c64 (ascii码:hello,world)
两条报文对比一下,因为固定报头中的标志位不同,影响了可变报头的内容,多了一个报文标识符;通过这3条报文数据的解析,相信你应该对mqtt协议有了一个比较清晰的认识了,如果想实现完整的协议,则请继续往后看。
了解熟悉mqtt协议后,就可以学着去实现mqtt协议,MQTT协议作为成熟、通用且使用广泛的协议,其实已经有很多的开源代码实现了其协议,学习借鉴这些优秀的开源代码,相信能给你开发出理想的mqtt应用带来非常多的帮助,以下是mqtt官方罗列的一些实现 ,点击这里查看
想要查看更详细、完整的MQTT协议,可以查看此份官方文档,每一条报文的详细实现都有非常明确的说明
下载MQTT 协议 3.1.1 中文版
有些小伙伴反馈,无法下载文档,如果无法下载,请前往百度网盘下载,百度网盘地址 , 提取码: bvk5
百度网盘地址 链接: https://pan.baidu.com/s/1iI75rW5El2BJ3exlkgddng?pwd=jpe1 提取码: jpe1,点击下载
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳
我有一个应用程序可以读取文件的内容并为其编制索引。我将它们存储在磁盘本身中,但现在我使用的是AmazonS3,因此以下方法不再适用。事情是这样的:defperform(docId)@document=Document.find(docId)if@document.file?#Youshould'tcreateanewversion@document.versionlessdo|doc|@document.file_content=Cloudoc::Extractor.new.extract(@document.file.file)@document.saveendendend@docu
我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit
文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co
我正在尝试在Rails上安装ruby,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf
文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,
SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手