当前位置: 首页 > news >正文

Netty ByteToMessageDecoder 中 ByteBuf 的readerIndex 详解

在Netty的ByteToMessageDecoder中,ByteBuf扮演了“原始数据的临时容器”和“解码操作的对象”的双重角色。它既缓存了不完整的网络数据包,也是进行解码操作时直接操作的对象。

一. ByteBuf的内部结构

二. 影响 readerIndex 的操作

(1) 前移 readerIndex(消耗数据):

byteBuf.readByte() // +1
byteBuf.readShort() // +2
byteBuf.readInt() // +4
byteBuf.readLong() // +8
byteBuf.readBytes(dst) // +dst.length
byteBuf.readBytes(dst, 0, 5) // +5
byteBuf.skipBytes(n) // +n
byteBuf.readCharSequence(n, charset) // +n

(2) 不改变 readerIndex:

byteBuf.getByte(index) // 绝对读取,不改 index
byteBuf.getInt(index) // 绝对读取,不改 index
byteBuf.getBytes(index, dst) // 绝对读取,不改 index
byteBuf.readableBytes() // 只查询
byteBuf.capacity() // 只查询
byteBuf.duplicate() // 共享数据,独立 index
byteBuf.slice() // 共享数据,独立 index
byteBuf.copy() // 深拷贝,完全独立

(3) 直接设置 readerIndex:

byteBuf.readerIndex(0) // 直接设置为 0
byteBuf.resetReaderIndex() // 回退到上次 markReaderIndex() 的位置
byteBuf.markReaderIndex() // 记录当前 readerIndex 位置(不改值)

(4) mark / reset 机制

byteBuf.markReaderIndex(); // 记住当前位置,比如 0
byteBuf.readByte(); // readerIndex: 0→1
byteBuf.readByte(); // readerIndex: 1→2
byteBuf.resetReaderIndex(); // 回到 0(必须先 mark,否则行为不确定)

没有 mark 就 reset 的行为:
- Netty 4.x 默认回退到 0(AbstractByteBuf 的默认 mark 是 0)
- 但这不是规范行为,依赖默认值是坏习惯

三. ByteToMessageDecoder 中的规范用法

ByteToMessageDecoder 的 decode() 方法有严格约定:

规则 1:添加了消息到 list,必须消费字节

// ❌ 错误:list.add() 了但 readerIndex 没动 → "did not read anything but decoded a message"
protected void decode(ctx, byteBuf, list) {
byteBuf.resetReaderIndex(); // readerIndex 回到 0
list.add(byteBuf); // 加了消息但没消费字节
}

// ✅ 正确:添加消息后标记字节已消费
protected void decode(ctx, byteBuf, list) {
ByteBuf data = byteBuf.retainedDuplicate();
list.add(data);
byteBuf.skipBytes(byteBuf.readableBytes()); // 标记已消费
}

规则 2:不处理就不要动 readerIndex,直接 return

// ✅ 正确:数据不够,等下次
protected void decode(ctx, byteBuf, list) {
if (byteBuf.readableBytes() < 2) {
return; // 不读字节,不改 readerIndex
}
...
}

规则 3:不要把内部 ByteBuf 直接传给下游

// ❌ 错误:传内部 buffer,下游释放后 decoder 再释放 → 双重释放
channelHandlerContext.fireChannelRead(byteBuf);

// ✅ 正确:用 retainedDuplicate() 创建副本
ByteBuf data = byteBuf.retainedDuplicate();
list.add(data);
byteBuf.skipBytes(byteBuf.readableBytes());

规则 4:mark 和 reset 必须配对

// ❌ 错误:没有 mark 就 reset
byteBuf.readByte();
byteBuf.resetReaderIndex(); // 行为不确定

// ✅ 正确:先 mark 再 reset
byteBuf.markReaderIndex();
byteBuf.readByte();
byteBuf.resetReaderIndex();

总结速查表

四. ByteBuf读取和消费有的区别

● 好问题。在大多数场景下,读取 = 消费。但在 Netty 中它们有区别:

读取 ≠ 消费 的场景

1. 读了但"退回" — 不算消费

byteBuf.markReaderIndex(); // readerIndex = 0,记住位置
byte b = byteBuf.readByte(); // readerIndex = 1,读了
byteBuf.resetReaderIndex(); // readerIndex = 0,退回去

// ByteToMessageDecoder 视角:readerIndex 没动 → 没消费任何字节

读了,但退回去等于没读。

2. 窥探但不移动 — 不算消费

byte b = byteBuf.getByte(0); // 看了一眼,readerIndex 还是 0

3. 读了但传给别人 — 谁消费?

byte[] data = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(data); // readerIndex 到底,自己消费了
channelHandlerContext.fireChannelRead(byteBuf); // 传空的给别人,没意义

// vs

ByteBuf copy = byteBuf.retainedDuplicate(); // 副本,readerIndex = 0
list.add(copy); // 副本给下游消费
byteBuf.skipBytes(byteBuf.readableBytes()); // 原 buffer 标记已消费

从 ByteToMessageDecoder 的视角理解

ByteToMessageDecoder 只看一件事:decode() 调用前后,readerIndex 有没有移动。

// 情况 A:readerIndex 移动了 → "消费了"
readerIndex before: 0
decode() { byteBuf.readByte(); } // readerIndex: 0→1
readerIndex after: 1
→ 消耗了 1 字节 ✓

// 情况 B:readerIndex 没动 → "没消费"
readerIndex before: 0
decode() { byteBuf.markReaderIndex(); byteBuf.readByte(); byteBuf.resetReaderIndex(); }
readerIndex after: 0
→ 消耗了 0 字节 ✗

// 情况 C:readerIndex 没动,但 list 有消息 → 报错!
readerIndex before: 0
decode() { byteBuf.resetReaderIndex(); list.add(byteBuf); }
readerIndex after: 0
→ "did not read anything but decoded a message" ❌

一句话总结

- 读取:访问数据的行为(readByte()、getByte() 都算)
- 消费:readerIndex 净前移了多少

mark + read + reset = 读了但没消费,就像你翻了一下书但合回去说"我没看"。

http://www.jsqmd.com/news/1204683/

相关文章:

  • ABAP Web Service集成SAML 2.0:从配置到用户映射的完整实战指南
  • Hermes便携IDE:U盘级AI Agent开发环境全平台部署指南
  • JWT双令牌认证与Redis黑名单:构建高可用分布式会话方案
  • Ventoy多系统启动U盘制作与优化全攻略
  • 【标题】2026年赤峰经济纠纷律师推荐 张洛溪7年民商法律实务经验 - 本地品牌推荐
  • C++多文件编程:从编译链接原理到项目工程化实践
  • C++性能优化实战:从硬件原理到代码调优的完整指南
  • 一文读懂 | 欧盟《网络弹性法案》(CRA)
  • agent学习Day5——Ruff、日志与结构化 LLM 输出
  • 冷柜温控器为什么要做远程监控和异常告警
  • Windows定时任务设置与schtasks命令详解
  • Python五子棋实战:从控制台到图形界面的完整开发指南
  • 人形机器人产品经理实战指南:从物理约束到场景落地
  • C++环境搭建全攻略:从零到一运行Hello World
  • 劳力士中国官方售后服务中心|地址与客户服务热线权威信息公示(2026年7月更新) - 劳力士服务中心
  • springboot新能源汽车充电桩预约与使用管理系统---附源码21146
  • 我见过最厉害的产品运营,都悄悄戒掉了“技巧”
  • C#动态调用C/C++ DLL:告别DLL地狱,实现优雅降级与跨平台部署
  • 先楫HPM5361EVK开发板与红外传感器集成开发指南
  • Windows 11 23H2架构解析与安装指南
  • Windows驱动开发必备:Windbg快捷键肌肉记忆训练指南
  • C++实现Raft共识算法:从状态机设计到异步工程实践
  • 2026 年现阶段凉山州靠谱的镀金镀银电子料回收制造企业深度解析,别再扔了!这批“废料”能帮你赚回多少钱? - 领域鉴赏官
  • VLA模型本质是动作片段搜索引擎,非通用机器人智能
  • C++ STL queue容器详解:从FIFO原理到多线程实战应用
  • Sherlock终极进化:从脚本到AI智能搜索平台的架构设计与实现
  • 激光雷达线数不是自动驾驶能力标尺:点云质量与系统协同才是关键
  • 2026年7月最新亨得利官方服务项目及价格查询|完整热线电话与网点地址权威信息公告 - 亨得利官方
  • 不同工况下带锯条齿型参数匹配与切割损耗优化研究
  • C++实战:从零构建Windows任务管理器,掌握系统编程核心技能