【MQTT】报文的结构:固定头、剩余长度与 UTF-8
用 Wireshark 抓过 MQTT 流量之后多半盯着报文列表发过呆第一条 CONNECT 开头是0x10 0x43加一串看起来随机的字节第二条 PUBLISH 开头是0x32。你大概知道第一个字节代表报文类型但0x10和0x32是什么关系0x43是哪来的如果一条消息很大剩余长度超过 127 是怎么塞进一个字节的再进一步如果 Topic 中带有中文字符客户端怎么知道字符串在哪里结束Broker 如何知道是否合法这些答案都在固定头Fixed Header里。MQTT 所有报文共享同一个头结构「类型 标志位 剩余长度」后面的载荷部分根据报文类型来设置。本文将沿着MQTTPacket.c的编解码函数逐个拆解最后拼出完整的 CONNECT 报文。这里先给出结论第一个字节高 4 位是报文类型低 4 位是标志位。CONNECT 报文类型是 1左移 4 位就是0x10。低 4 位只有在 PUBLISH 里才有含义DUP/QoS/RETAIN其它报文类型的标志位是保留位3.1.1 收到非零值算协议错误。剩余长度可变长编码每个字节 7 位有效数据 最高位表示后面是否还有数据最多 4 字节可以表示 256MB 长度。MQTTPacket_encode中实现了剩余长度的编码逻辑。MQTT 字符串2 字节大端长度 UTF-8 字节组成。Paho 用MQTTLenString结构来保存源码utf-8.c负责校验合法性。固定头与剩余长度第一个字节类型在高 4 位标志在低 4 位MQTT 报文的第一个字节是固定头的身份字节8 个 bit 分为两部分bit 7 6 5 4 │ bit 3 2 1 0 Type │ Flags高 4 位是报文类型低 4 位是标志位。内存布局上「类型在高位」所以类型值要左移 4 位才能得到最终字节这就是为什么CONNECTtype1的字节是0x10而不是0x01。PUBLISH是0x30type3、PUBACK是0x40type4。代码里的结构长这样typedefunion{/*unsigned*/charbyte;/** the whole byte */struct{bit retain:1;/** retained flag bit */unsignedintqos:2;/** QoS value, 0, 1 or 2 */bit dup:1;/** DUP flag bit */unsignedinttype:4;/** message type nibble */}bits;}Header;Header在MQTTPacket.h里只是一个字节语义上再拆成type/flags两个 4 位。其中标志位只有在 PUBLISH 里才有实际含义bit3是 DUP重发标记bit2-1是 QoS 级别0/1/2bit0是 RETAIN。其它 14 种报文的标志位都是协议保留位MQTT 3.1/3.1.1 要求它们必须为 0收到非零值视为协议错误MQTT 5.0 对几个报文CONNACK 的 session present、SUBACK 等放宽了部分位但总体上「非 PUBLISH 的 flags 默认全 0」仍成立抓包时看第一个字节的低 4 位可以判断是不是 PUBLISH、以及 QoS 是多少0x32 PUBLISH QoS 10x30 | 0x020x3A PUBLISH QoS 1 DUP0x30 | 0x08 | 0x02。剩余长度1 到 4 字节的可变长编码第一个字节之后是剩余长度Remaining Length固定头之后还剩多少字节。它不用固定 4 字节而是 1-4 字节的可变长编码每个字节用 7 位存数据最高位0x80表示「后面还有字节」1 字节最多 1272 字节最多 163833 字节最多 20971514 字节最多 268435455256 MB超过 4 字节就是非法报文剩余长度通过MQTTPacket_encode计算// paho.mqtt.c/src/MQTTPacket.c:302-317intMQTTPacket_encode(char*buf,size_tlength){intrc0;do{chardlength%128;// 取低 7 位length/128;// 剩下的右移128 进制一位if(length0)d|0x80;// 还有后续字节 → 置最高位if(buf)buf[rc]d;// 输出一个字节elserc;}while(length0);returnrc;}通过几个例子来理解编码格式长度字节序列说明00x00无载荷如 PINGREQ1270x7F1 字节上限1280x80 0x01128 % 128 0最高位置 1128 / 128 1进下一字节163830xFF 0x7F2 字节上限2684354550xFF 0xFF 0xFF 0x7F4 字节上限Paho 实现解码按照从字节流中每次读取一个字节来解析如果最高位是 1 就继续向后读否则就停止解析如果超过了 4 字节报错非法报文。// paho.mqtt.c/src/MQTTPacket.c:1081-1099intMQTTPacket_VBIdecode(int(*getcharfn)(char*,int),unsignedint*value){charc;intmultiplier1;intlen0;#defineMAX_NO_OF_REMAINING_LENGTH_BYTES4*value0;do{if(lenMAX_NO_OF_REMAINING_LENGTH_BYTES)returnMQTTPACKET_READ_ERROR;// 超过 4 字节非法数据(*getcharfn)(c,1);*value(c127)*multiplier;// 当前 7 位乘上 128 的幂multiplier*128;}while((c128)!0);// 最高位为 1 就继续读returnlen;}Paho 提供了两个解码函数MQTTPacket_decodeMQTTPacket.c:330从 socket 直接读MQTTPacket_decodeBufMQTTPacket.c:1121从内存 buffer 读。MQTTPacket_decode用于MQTTPacket_Factory收包边读边解码先读一个字节判断剩余长度占几字节再读够整包。MQTTPacket_decodeBuf用于报文已经在内存里的场景比如 WebSocket 或代理隧道转发。MQTTPacket_decode的关键在于「先知道长度才能把整包读出来」它按需逐字节读等最高位为 0 才确定剩余长度占了几字节然后read()凑齐payload。剩余长度编码有个「0 陷阱」0x80 0x00这种多余的前导零字节虽然按照存储算法上等于 0但按规范是非法的编码必须用最短形式。Paho 的解码循环遇到0x80会继续读下一个字节读到 0 结束算出长度 128×000。不会崩溃或异常但严格校验的 Broker 会拒绝这类报文。自己构造报文时也别这么写。字符串2 字节长度 UTF-8MQTT 字符串的字节格式MQTT 里的字符串topic、clientID、username、password 等不是 C 字符串那样「以 \0 结尾」而是一个固定格式0-1 字节长度大端2 字节单位是字节数 2~ 字节UTF-8 编码的内容以async_publish示例中发布的 topichello为例在报文里是00 05 68 65 6c 6c 6f长度 5 5 个ASCII 字节。注意长度使用大端高字节在前存储。长度只占 2 字节所以 MQTT 字符串最长 65535 字节这是协议规定的上限超出的 topic/clientID 在编码时就会被截断。MQTTLenString长度 指针内存中用MQTTLenString表示字符串定义在MQTTProperties.h中// paho.mqtt.c/src/MQTTProperties.h:88-93typedefstruct{intlen;/** the length of the string */char*data;/** pointer to the string data */}MQTTLenString;字段是len 指针data不是char[n]数组。好处是零拷贝收包时data直接指向接收缓冲里字符串的起始位置len标记边界不需要为每个字符串做malloc 拷贝。代价是不保证以\0结尾用strlen、printf %s当 C 字符串处理可能越界读。编码方向把{len, data}落成报文字节// paho.mqtt.c/src/MQTTPacket.c:1023-1027voidwriteMQTTLenString(char**pptr,MQTTLenString lenstring){writeInt(pptr,lenstring.len);// 2 字节大端长度memcpy(*pptr,lenstring.data,lenstring.len);// 内容紧跟着*pptrlenstring.len;}解码方向MQTTLenStringRead先确认缓冲里还剩足够字节读完长度后再用「字符串终点 ≤ 缓冲终点」兜底防止报文被截断时越界// paho.mqtt.c/src/MQTTPacket.c:1031-1049intMQTTLenStringRead(MQTTLenString*lenstring,char**pptr,char*enddata){intlen-1;if(enddata-(*pptr)1)// 够读 2 字节长度{lenstring-lenreadInt(pptr);// 读长度pptr 偏移 2 字节if((*pptr)[lenstring-len]enddata)// 内容没超出缓冲{lenstring-data(char*)*pptr;// 零拷贝直接指过去*pptrlenstring-len;len2lenstring-len;}}returnlen;}注意enddata贯穿整个 Packet 层的解码所有变长字段都是通过「指针推进 终点不越界」的方式读写读源码看到enddata就知道是防截断用的。零拷贝指针直接指向内存固然快但是 C 程序中的内存泄露、野指针和内存重复释放也是令人头疼的问题。我们在使用这种方式编程时一定要有一套统一的内存释放规则避免多处释放或者忘记释放内存。UTF-8 校验为什么必须有这一层报文里写「UTF-8」不只是声明3.1.1 规范要求收发双方校验topic 等字符串必须是合法 UTF-8且不能包含 U0000NUL、代理区 UD800~UDFFF、以及超过 U10FFFF 的编码。这些字符会破坏订阅匹配和显示所以协议直接禁止。Paho 在utf-8.c中校验实现「逐字符判定字节数 查合法范围表」// paho.mqtt.c/src/utf-8.c:76-119节选staticconstchar*UTF8_char_validate(intlen,constchar*data){intcharlen2;/* 先定这个字符占几个字节 */if((data[0]128)0)charlen1;// 0xxxxxxx → 1 字节elseif((data[0]0xF0)0xF0)charlen4;// 11110xxx → 4 字节elseif((data[0]0xE0)0xE0)charlen3;// 1110xxxx → 3 字节// 其余 110xxxxx → 2 字节if(charlenlen)gotoexit;// 声称的字节数超过了实际长度/* 用合法范围表逐字节核对比如代理区、超范围编码都会被拒 */for(i0;iARRAY_SIZE(valid_ranges);i){/*...*/}}UTF8_validateString在创建客户端、连接、订阅、取消订阅、发布这 5 个流程的入口校验 clientId、username、password、topic 的 UTF-8 编码合法性校验失败认为报文非法返回错误。亲手拼出一个 CONNECT我们把前面的内容串起来拼一个最简单的 MQTT 3.1.1 CONNECT 报文clientID paho_test、keepalive 60、clean session true无 will、无用户名密码。第一步算载荷。 3.1.1 的 CONNECT 载荷只有一项 clientID载荷 UTF-8 字符串 paho_test 2 字节长度 9 字节内容 11 字节第二步算可变头。 3.1.1 的 CONNECT 可变头四段协议名 MQTT → 00 04 M Q T T 24 6 字节 协议级别 4 → 04 1 字节 Connect Flags → 02 bit1 clean session 1 字节 Keep Alive 60 → 00 3C 2 字节可变头共6112 10字节载荷 11 字节所以剩余长度 21 0x15小于 1271 字节就够。第三步拼固定头。 第一字节0x10type1 左移 4 位flags 全 0第二字节剩余长度0x15。完整的 23 字节报文10 15 00 04 4D 51 54 54 04 02 00 3C 00 09 70 61 68 6F 5F 74 65 73 74 │ │ └──────┬──────┘ │ │ │ │ └──────┬──────┘ └──────┬──────┘ └ └ 协议名 MQTT │ │ │ keepalive │ clientID paho_test9 字节 固定头 └级─└flags └功能全在低 4 位、字符串全在 (11字节) 2 字节长度 UTF-8 里通过nc命令向 Broker 发送报文来验证# 中间字节省略自行填充printf\x10\x15\...|nc127.0.0.11883报文成功发送并收到 Broker 的 CONNACK 连接成功的报文。MQTT 5.0 的 CONNECT 在可变头里多了一段 propertieskeepalive 之后、载荷之前。总结回看开头的三个结论第一字节 类型 4 | flags所以 CONNECT 是0x10低 4 位只有 PUBLISH 有含义剩余长度是 128 进制的可变长编码1 到 4 字节、上限 256MB编解码函数MQTTPacket_encode/MQTTPacket_VBIdecode字符串 2 字节大端长度 UTF-8MQTTLenString用「长度 指针」零拷贝承载utf-8.c 负责 3.1.1 的合法性校验。现在你能看懂任意 MQTT 报文的第一个字节和长度字段了。下一篇「第一次握手CONNECT/CONNACK 与版本协商」将拼好的 CONNECT 报文发出去追踪从 C 的connect()到 Broker 回 CONNACK 的完整链路参数校验、CONNECT 报文构造、TCP 发送、以及客户端与 Broker 之间的版本协商逻辑。

相关新闻

7.LeetCode算法习题讲解--双指针--四数之和

7.LeetCode算法习题讲解--双指针--四数之和

一.题目 习题链接:18. 四数之和 - 力扣(LeetCode) 二.题目分析 给定数组 nums 和目标值 target,寻找所有不重复的四元组 [nums[a], nums[b], nums[c], nums[d]],满足: 下标约束:0 ≤ a,b,c,d…

2026/8/23 9:01:45 阅读更多 →
数据结构与算法实战指南:从Redis到排序,避坑性能陷阱与进阶应用

数据结构与算法实战指南:从Redis到排序,避坑性能陷阱与进阶应用

1. 从“能跑就行”到“心中有数”:为什么数据结构与算法是开发者的分水岭 我见过太多这样的场景:一个功能,用最朴素的数组和循环嵌套,吭哧吭哧写了上百行,跑起来也勉强能用。直到某天数据量从一千变成一百万&#xff0…

2026/8/24 16:36:48 阅读更多 →
Revit参数化设计实战:从自适应构件到概念体量的完整工作流解析

Revit参数化设计实战:从自适应构件到概念体量的完整工作流解析

如果你是一名建筑设计师或BIM工程师,正在学习Revit,那么“参数化设计”这个概念你一定不陌生。但你是否遇到过这样的困境:教程里的概念都懂,一到实际项目,面对复杂的幕墙、异形屋顶或自适应构件,就不知道从…

2026/8/24 16:23:31 阅读更多 →

最新新闻

如何把RDT2部署到自定义机器人平台?面向其他本体与末端执行者的进阶部署路线图

如何把RDT2部署到自定义机器人平台?面向其他本体与末端执行者的进阶部署路线图

如何把RDT2部署到自定义机器人平台?面向其他本体与末端执行者的进阶部署路线图 【免费下载链接】RDT2 Official code of RDT 2 项目地址: https://gitcode.com/gh_mirrors/rd/RDT2 RDT2 是面向具身智能的 VLA(视觉-语言-动作)基础模型…

2026/8/24 17:02:20 阅读更多 →
适配器模式深度解析:从设计模式到架构思维的实战指南

适配器模式深度解析:从设计模式到架构思维的实战指南

1. 项目概述:重新认识“配接器”的价值如果你在软件开发、硬件设计或者系统集成的领域里摸爬滚打过一段时间,那么“配接器”(Adapter)这个词对你来说一定不陌生。它可能出现在你阅读的某个设计模式文档里,也可能静静地…

2026/8/24 17:02:20 阅读更多 →
BetterGI 新增云原神 PC 客户端支持

BetterGI 新增云原神 PC 客户端支持

BetterGI 新增云原神 PC 客户端支持 【免费下载链接】better-genshin-impact 📦BetterGI 更好的原神 - 自动拾取 | 自动剧情 | 全自动钓鱼(AI) | 全自动七圣召唤 | 自动伐木 | 自动刷本 | 自动采集/挖矿/锄地 | 一条龙 | 全连音游 | 自动烹饪 - UI Automation Test…

2026/8/24 17:02:20 阅读更多 →
AI智能体基准测试:如何客观评估端到端优化研发能力

AI智能体基准测试:如何客观评估端到端优化研发能力

1. 项目概述与核心价值最近在AI研发圈子里,关于“智能体”的讨论热度一直居高不下,但一个核心痛点始终存在:我们如何客观、公正地评价一个AI智能体,尤其是那些号称能解决复杂业务优化问题的“端到端研发智能体”的真实水平&#x…

2026/8/24 17:02:20 阅读更多 →
微软面试模拟题全解析:从算法到系统设计的实战备考指南

微软面试模拟题全解析:从算法到系统设计的实战备考指南

1. 项目概述:为什么我们需要“微软面试模拟题”? 如果你正在准备微软的面试,或者任何一家顶级科技公司的技术面,你大概率已经听过“刷题”这个词。但“刷题”和“高效模拟面试”是两回事。前者是机械地解决孤立问题,后…

2026/8/24 17:02:20 阅读更多 →
30分钟读完5篇arXiv论文?ChatPaper 论文总结与文献阅读完整指南

30分钟读完5篇arXiv论文?ChatPaper 论文总结与文献阅读完整指南

30分钟读完5篇arXiv论文?ChatPaper 论文总结与文献阅读完整指南 【免费下载链接】ChatPaper Use ChatGPT to summarize the arXiv papers. 全流程加速科研,利用chatgpt进行论文全文总结专业翻译润色审稿审稿回复 项目地址: https://gitcode.com/gh_mir…

2026/8/24 17:01:20 阅读更多 →

日新闻

前端内容安全与依赖审计实践

前端内容安全与依赖审计实践

前端内容安全与依赖审计实践 前端安全依赖分层防护。没有任何单一配置能替代输出编码、权限校验和依赖更新。 把不可信内容当作数据 默认使用框架的转义能力;确需渲染 HTML 时,先在服务端或可信的客户端库中进行白名单过滤。避免把用户输入直接赋给 inne…

2026/8/24 1:08:15 阅读更多 →
Windows登录密码存储机制全解析:从哈希算法到安全加固实战

Windows登录密码存储机制全解析:从哈希算法到安全加固实战

1. 项目概述:Windows登录密码的“黑匣子”每次你按下CtrlAltDel,输入密码,然后看到那个熟悉的桌面,这背后发生了一系列复杂而精密的操作。作为一名长期与Windows系统打交道的从业者,我经常被问到:“我的密码…

2026/8/24 1:08:15 阅读更多 →
AI面试系统安全挑战与解决方案

AI面试系统安全挑战与解决方案

1. 项目概述:AI面试系统的安全挑战去年参与某跨国企业AI面试系统部署时,遇到一个典型案例:候选人在视频面试中无意提到竞争对手产品名称,系统竟自动将该信息关联到企业知识库并生成竞品分析报告。这个看似"智能"的功能&…

2026/8/24 1:08:15 阅读更多 →

周新闻

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/24 0:06:02 阅读更多 →
SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/24 0:20:20 阅读更多 →
Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/24 0:14:11 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/23 18:47:06 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/23 12:10:44 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/24 11:20:22 阅读更多 →