惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tenable Blog
月光博客
月光博客
雷峰网
雷峰网
WordPress大学
WordPress大学
博客园 - 司徒正美
Last Week in AI
Last Week in AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
Visual Studio Blog
H
Help Net Security
Engineering at Meta
Engineering at Meta
Google DeepMind News
Google DeepMind News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
S
Security @ Cisco Blogs
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
爱范儿
爱范儿
W
WeLiveSecurity
J
Java Code Geeks
Forbes - Security
Forbes - Security
H
Hacker News: Front Page
T
Threatpost
The Cloudflare Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
N
Netflix TechBlog - Medium
Latest news
Latest news
V2EX - 技术
V2EX - 技术
小众软件
小众软件
T
The Blog of Author Tim Ferriss
A
Arctic Wolf
B
Blog RSS Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
I
InfoQ
C
Check Point Blog
N
News | PayPal Newsroom
Cyberwarzone
Cyberwarzone
V
V2EX
TaoSecurity Blog
TaoSecurity Blog
P
Privacy & Cybersecurity Law Blog
Microsoft Security Blog
Microsoft Security Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
D
DataBreaches.Net
F
Fortinet All Blogs
阮一峰的网络日志
阮一峰的网络日志
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
IT之家
IT之家
K
Kaspersky official blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Google DeepMind News
Google DeepMind News
C
CXSECURITY Database RSS Feed - CXSecurity.com
www.infosecurity-magazine.com
www.infosecurity-magazine.com

风萧古道 - 勤学苦练,年复一年

游戏服务器开发经验(五)应对复杂需求 沉浸式体验东汉末年生活 - 《真三国无双 起源》玩后感 怒其不争!致2025年HLTV的Top18-NiKo Linux家用服务器维护指南 游戏服务器开发经验(四)避免写Bug的习惯、技巧和心态 游戏服务器开发经验(三)线上维护 游戏服务器开发经验(二)避免内存泄露 游戏服务器开发经验(一)道具防刷 35岁找不到工作,绝对不会是软件开发人员的结局 用爱发电项目开发两个月的心得体会 以魏延“子午谷奇谋”讨论软件需求可行性问题 关于游戏服务器配置表功能的探讨 Java并发编程中上锁的几种方式 如何用C++分割一个字符串? CSAPP第二章-信息的表示与处理 我的自我介绍 Windows XP虚拟机中文版无需激活下载 Java TreeSet的一些用法和特性 Linux C++ Socket实战 传统软件服务器与游戏服务器架构区别 独立个人项目开发心得 - 任务切分、挑战性、实用性和半途而废 使用Python实现简单UDP Ping 使用Python开发一个简单的web服务器 Kotlin手动实现一个最简单的哈希表 Kotlin实现二叉堆、大顶堆、优先级队列 搭建Spark实战环境(3台linux虚拟机集群)(一)样板机的搭建 Springboot操作MongoDB,包括增改查及复杂操作 Unison在Linux下的安装与使用 Java实现类似WINSCP访问远程Linux服务器,执行命令、上传文件、下载文件 一个被废弃的项目——自动爬取信息然后发给我自己邮箱上 Python连接MongoDB和Oracle实战 MongoDB常用查询语句 vue和springboot项目部署到Linux服务器 Python的一些用法(可能不定时更新) java正则表达式 - 双反斜杠(\)和Pattern的matches()与find() 简述爬虫对两种网站的不同爬取方式 Vue的路由配置及手动改地址栏为啥又跳转回来?? [JavaScript]JS基础知识 [Mybatis]逆向工程中Select语句查询不出‘TEXT’字段 [编译原理]FIRST、FOLLOW和SELECT [Spring]Spring学习笔记 [算法]分布估计算法 - 一种求解多维背包问题的混合分布估计算法_王凌 [日常]我做独立博客的原因 人间值得 深入理解计算机系统 想想就开心! 最重要的事,只有一件 藏书 被讨厌的勇气 关于 简历 朋友 尊重自己:给予与接收的心灵艺术
MQTT协议中可变长度的具体计算方式(有计算过程解析)
JonathanLin · 2023-12-26 · via 风萧古道 - 勤学苦练,年复一年

本文介绍MQTT协议中,固定报头中的可变长度部分的计算方式。通过提供一些例子,将其他介绍MQTT协议的文章中没有仔细说明的计算部分进行解释。

参考文章

MQTT 5.0 报文(Packets)入门指南

这篇文章简要介绍了MQTT,但是没有明确提供计算过程。

MQTT简介之三(3) MQTT协议 报文的剩余长度如何计算和编码

这篇文章也介绍了MQTT,且有提供计算过程,但是是用一大段文字进行表述的,理解起来有些困难。

至于New Bing(GPT4.0)和ChatGPT 3.5,他们在做这种特殊规则下的二进制计算时,效果并不好。

如果在读完本文后你对二进制有兴趣,可以阅读这篇文章:CSAPP第二章-信息的表示与处理

如何计算可变长度?

以下是各大文章的相似描述:

MQTT协议的剩余长度字段使用了一种变长编码方案,每个字节的最高位是一个进位标志位,如果为1,表示还有后续的字节;如果为0,表示这是最后一个字节。每个字节的低七位是实际的数据位,用来表示剩余长度的一部分。因此,为了得到剩余长度的完整值,需要循环读取每个字节,并用一个乘法器来计算出总和。

并且,提供了一张表,介绍剩余长度在不同的区间中时,需要使用多少个字节数:

字节数最小值最大值
10(0x00)127(0x7F)
2128(0x80,0x01)16383(0xFF,0x7F)
316384(0x80,0x80,0x01)2097151(0xFF,0xFF,0x7F)
42097152(0x80,0x80,0x80,0x01)268435455(0xFF,0xFF,0xFF,0x7F)

读取过程分析

首先,我们明确MQTT协议读取数据的过程,是一个字节一个字节依次读取的。即读取完一个字节之后,再处理下一个字节。

那么为了可以动态控制可变长度部分的字节数,MQTT协议约定该部分的字节的第一位来标记“后续是否还有字节”。

明确这一点之后,我们抛开第一位,读取后七位的值,可以得到一个数。

想象一下,如果我们的可变长度res特别长,在2097152~2684354455之间,那么我们需要4个字节进行传输。此时我们将每个字节的第一位剥离,将剩余7位取出:

用变量a表示第一个字节后7位的数,用变量b表示第二个字节后7位的数,cd以此类推。

那么我们会有:res = a * 1 + b * 128 + c * 128 * 128 + d * 128 * 128 * 128

或者咱们换一种写法:res = a * 1280 + b * 1281 + c * 1282 + d * 1283

可以理解为,这是一个“公式”,即可变长度 = 各个字节除1以外的数 * 128字节序号-1 (不会打求和符号,而且也不标准,请以理解为主)

公式验算

以CSDN那篇文章的第四个例子为例,进行“公式”验算:

100000000,

100000000/128 = 781250,不等于0需要进位,

然后 781250/128=6103,不等于0再进一位,

然后 6103/128=47,不等于0再进一位

47/128=0,等于0无需再进位了

所以总体进3位,需要4个字节

第一个字节bit7是1, 第2个字节bit7是1,第3个字节bit7是1.,第4个字节bit7是0.

然后100000000%128=0,就是0x00,所以第一个字节bit0~bit6就是0x00,

然后781250%128=66,所以第2个字节bit0~bit6就是0x42,

然后 6103%128=87,所以第3个字节bit0~bit6就是0x57,

然后 47%128=47,所以第4个字节bit0~bit6就是0x2F,

然后再结合上bit7的话,就是0x80,0xC2,0xD7,0x2F

先抛开中间的计算过程,将它的目标和结果拿出来:目标100000000,结果0x80,0xC2,0xD7,0x2F

换算成二进制是:1000 0000 1100 0010 1101 0111 0010 1111

套用公式,一一计算a, b, c, d:

  • a: 1000 0000 –去除开头1–> 000 0000 –得到值–> 0
  • b: 1100 0010 –去除开头1–> 100 0010 –得到值–> 66
  • c: 1101 0111 –去除开头1–> 101 0111 –得到值–> 87
  • d: 0010 1111 –去除开头0–> 010 1111 –得到值–> 47

然后计算,a * 1 + b * 128 + c * 128 * 128 + d * 128 * 128 * 128 = 0 + 8448 + 1425408 + 98566144 = 100000000

如此一来,验证通过。

其他情况计算

在理解了4个字节的情况之后,我们会发现,前面读到的数字乘的值会比较小,后面读到的数字乘的值会比较大。因此就很容易理解为什么其他文章说0~127的数字只需要一个字节了,因为当可变长度区仅有一个字节时,乘数是1,而一个字节抛开第一位之后剩余7位,不考虑负数的情况下最大能表示的值就是127。

如果超过127,就需要从前往后的第二个字节进行处理,因为第二个字节(除第一位的剩余7位的输)默认要乘以128。

两个字节的存储长度区间是128~16383,如果大于16383,则需要使用三个字节存储,第三个字节(除第一位的剩余7位的输)默认要乘以128*128。

理解了这个之后,就可以通过上面的推算过程,反推为什么100000000要进行多次除以128的操作。

代码实现

在mqant中,官方提供了一种读取过程,将其抄录如下:

	// Read the high
	multiplier := 1
	for {
		count_len++
		pack.length += (int(temp_byte&127) * multiplier)
		if temp_byte>>7 != 0 && count_len < 4 {
			temp_byte, err = r.ReadByte()
			if err != nil {
				return
			}
			multiplier *= 128
		} else {
			break
		}
	}

可以看到,这里定义了变量multiplier,其初始值是1,且读取完一个字节之后,将其自己*=128扩大,满足前述的“公式”。

总结

在TCP完成可靠传输之上,还需要应用层进行拆包解析。通过先读取包长度的方式,可以让后续数据传输时监测是否传输成功。而MQTT使用1~4个字节的方式,灵活配置可变长度,其思路值得学习。