Netty ByteToMessageDecoder 中 ByteBuf 的readerIndex 详解 在Netty的ByteToMessageDecoder中ByteBuf扮演了“原始数据的临时容器”和“解码操作的对象”的双重角色。它既缓存了不完整的网络数据包也是进行解码操作时直接操作的对象。一. ByteBuf的内部结构二. 影响 readerIndex 的操作(1) 前移 readerIndex消耗数据byteBuf.readByte() // 1byteBuf.readShort() // 2byteBuf.readInt() // 4byteBuf.readLong() // 8byteBuf.readBytes(dst) // dst.lengthbyteBuf.readBytes(dst, 0, 5) // 5byteBuf.skipBytes(n) // nbyteBuf.readCharSequence(n, charset) // n(2) 不改变 readerIndexbyteBuf.getByte(index) // 绝对读取不改 indexbyteBuf.getInt(index) // 绝对读取不改 indexbyteBuf.getBytes(index, dst) // 绝对读取不改 indexbyteBuf.readableBytes() // 只查询byteBuf.capacity() // 只查询byteBuf.duplicate() // 共享数据独立 indexbyteBuf.slice() // 共享数据独立 indexbyteBuf.copy() // 深拷贝完全独立(3) 直接设置 readerIndexbyteBuf.readerIndex(0) // 直接设置为 0byteBuf.resetReaderIndex() // 回退到上次 markReaderIndex() 的位置byteBuf.markReaderIndex() // 记录当前 readerIndex 位置不改值(4) mark / reset 机制byteBuf.markReaderIndex(); // 记住当前位置比如 0byteBuf.readByte(); // readerIndex: 0→1byteBuf.readByte(); // readerIndex: 1→2byteBuf.resetReaderIndex(); // 回到 0必须先 mark否则行为不确定没有 mark 就 reset 的行为- Netty 4.x 默认回退到 0AbstractByteBuf 的默认 mark 是 0- 但这不是规范行为依赖默认值是坏习惯三. ByteToMessageDecoder 中的规范用法ByteToMessageDecoder 的 decode() 方法有严格约定规则 1添加了消息到 list必须消费字节// ❌ 错误list.add() 了但 readerIndex 没动 → did not read anything but decoded a messageprotected void decode(ctx, byteBuf, list) {byteBuf.resetReaderIndex(); // readerIndex 回到 0list.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());规则 4mark 和 reset 必须配对// ❌ 错误没有 mark 就 resetbyteBuf.readByte();byteBuf.resetReaderIndex(); // 行为不确定// ✅ 正确先 mark 再 resetbyteBuf.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 还是 03. 读了但传给别人 — 谁消费byte[] data new byte[byteBuf.readableBytes()];byteBuf.readBytes(data); // readerIndex 到底自己消费了channelHandlerContext.fireChannelRead(byteBuf); // 传空的给别人没意义// vsByteBuf copy byteBuf.retainedDuplicate(); // 副本readerIndex 0list.add(copy); // 副本给下游消费byteBuf.skipBytes(byteBuf.readableBytes()); // 原 buffer 标记已消费从 ByteToMessageDecoder 的视角理解ByteToMessageDecoder 只看一件事decode() 调用前后readerIndex 有没有移动。// 情况 AreaderIndex 移动了 → 消费了readerIndex before: 0decode() { byteBuf.readByte(); } // readerIndex: 0→1readerIndex after: 1→ 消耗了 1 字节 ✓// 情况 BreaderIndex 没动 → 没消费readerIndex before: 0decode() { byteBuf.markReaderIndex(); byteBuf.readByte(); byteBuf.resetReaderIndex(); }readerIndex after: 0→ 消耗了 0 字节 ✗// 情况 CreaderIndex 没动但 list 有消息 → 报错readerIndex before: 0decode() { byteBuf.resetReaderIndex(); list.add(byteBuf); }readerIndex after: 0→ did not read anything but decoded a message ❌一句话总结- 读取访问数据的行为readByte()、getByte() 都算- 消费readerIndex 净前移了多少mark read reset 读了但没消费就像你翻了一下书但合回去说我没看。