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

推荐订阅源

博客园 - Franky
N
Netflix TechBlog - Medium
宝玉的分享
宝玉的分享
Google DeepMind News
Google DeepMind News
腾讯CDC
G
Google Developers Blog
Martin Fowler
Martin Fowler
Microsoft Security Blog
Microsoft Security Blog
Recent Announcements
Recent Announcements
爱范儿
爱范儿
Engineering at Meta
Engineering at Meta
Microsoft Azure Blog
Microsoft Azure Blog
A
About on SuperTechFans
aimingoo的专栏
aimingoo的专栏
有赞技术团队
有赞技术团队
Jina AI
Jina AI
人人都是产品经理
人人都是产品经理
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence
罗磊的独立博客
博客园 - 三生石上(FineUI控件)
美团技术团队
WordPress大学
WordPress大学
阮一峰的网络日志
阮一峰的网络日志

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

AI 编程时代,想象力成了最被奖励的能力 游戏服务器开发经验(五)应对复杂需求 沉浸式体验东汉末年生活 - 《真三国无双 起源》玩后感 怒其不争!致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服务器,执行命令、上传文件、下载文件
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个字节的方式,灵活配置可变长度,其思路值得学习。