Netty编码器深度解析 - 详细版
一、编码器与解码器的对称关系
1.1 数据流向对比
入站(Inbound)- 解码器:
网络字节流 → ByteToMessageDecoder → Java对象 → 业务Handler
出站(Outbound)- 编码器:
业务Handler → Java对象 → MessageToByteEncoder → 网络字节流
1.2 Handler类型对比
// 解码器:处理入站数据
public abstract class ByteToMessageDecoder
extends ChannelInboundHandlerAdapter {
// 重写 channelRead() 方法
}
// 编码器:处理出站数据
public abstract class MessageToByteEncoder<I>
extends ChannelOutboundHandlerAdapter {
// 重写 write() 方法
}
关键区别:
- 解码器继承
ChannelInboundHandlerAdapter,处理入站事件 - 编码器继承
ChannelOutboundHandlerAdapter,处理出站事件
二、MessageToByteEncoder 深度解析
2.1 核心工作流程
当你调用 ctx.write(msg) 或 channel.write(msg) 时,编码器的工作流程:
1. 用户调用: ctx.write(myObject)
↓
2. Pipeline 出站传播
↓
3. MessageToByteEncoder.write() 被调用
↓
4. 检查消息类型: acceptOutboundMessage(msg)
↓
5. 分配 ByteBuf: allocateBuffer()
↓
6. 调用 encode(): 将对象写入 ByteBuf
↓
7. 释放原始消息: ReferenceCountUtil.release(msg)
↓
8. 传递 ByteBuf: ctx.write(buf, promise)
2.2 write() 方法源码详解
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
ByteBuf buf = null;
try {
// 步骤1:类型检查
if (this.acceptOutboundMessage(msg)) {
// 类型匹配,进行编码
I cast = (I) msg;
// 步骤2:分配 ByteBuf
buf = this.allocateBuffer(ctx, msg, this.preferDirect);
try {
// 步骤3:编码(核心方法)
this.encode(ctx, cast, buf);
} finally {
// 步骤4:释放原始消息
ReferenceCountUtil.release(msg);
}
// 步骤5:传递编码后的数据
if (buf.isReadable()) {
// ByteBuf 有数据,传递给下一个Handler
ctx.write(buf, promise);
} else {
// ByteBuf 为空,传递空缓冲区
buf.release();
ctx.write(Unpooled.EMPTY_BUFFER, promise);
}
buf = null;
} else {
// 类型不匹配,直接传递
ctx.write(msg, promise);
}
} catch (EncoderException e) {
throw e;
} catch (Throwable t) {
throw new EncoderException(t);
} finally {
// 清理资源
if (buf != null) {
buf.release();
}
}
}
2.3 关键方法详解
2.3.1 acceptOutboundMessage() - 类型匹配
public boolean acceptOutboundMessage(Object msg) throws Exception {
return this.matcher.match(msg);
}
作用:检查消息类型是否匹配编码器的泛型类型。
示例:
// 定义编码器
public class IntegerEncoder extends MessageToByteEncoder<Integer> {
// 泛型 I = Integer
}
// 使用场景
ctx.write(123); // ✓ 匹配,会调用 encode()
ctx.write("Hello"); // ✗ 不匹配,直接传递给下一个Handler
ctx.write(45.6); // ✗ 不匹配,直接传递
2.3.2 allocateBuffer() - 分配缓冲区
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx,
I msg,
boolean preferDirect) {
return preferDirect ?
ctx.alloc().ioBuffer() : // 直接内存
ctx.alloc().heapBuffer(); // 堆内存
}
preferDirect 参数详解:
// 构造函数
protected MessageToByteEncoder() {
this(true); // 默认使用直接内存
}
protected MessageToByteEncoder(boolean preferDirect) {
this.preferDirect = preferDirect;
}
直接内存 vs 堆内存:
直接内存(Direct Buffer):
优点:
- 零拷贝,直接与网络IO交互
- 适合网络传输
缺点:
- 分配和释放开销大
- 不受JVM GC管理
堆内存(Heap Buffer):
优点:
- 分配快速
- 受JVM GC管理
缺点:
- 需要拷贝到直接内存才能进行网络IO
选择建议:
- 网络传输场景:使用直接内存(默认)
- 临时数据处理:使用堆内存
三、MessageToByteEncoder 实战示例
3.1 基础示例:Short 编码器
public class ShortToByteEncoder extends MessageToByteEncoder<Short> {
@Override
protected void encode(ChannelHandlerContext ctx, Short msg, ByteBuf out) {
// 将 Short 写入 ByteBuf(占用2字节)
out.writeShort(msg);
}
}
数据流演示:
输入: Short 值 = 258 (0x0102)
encode() 执行:
out.writeShort(258)
ByteBuf 状态:
索引: 0 1
┌─────┬─────┐
数据: │ 01 │ 02 │
└─────┴─────┘
输出: ByteBuf [0x01, 0x02]
3.2 进阶示例:自定义协议编码器
假设我们要实现这个协议:
+--------+--------+------------+
| Length | Type | Data |
| 4 bytes| 1 byte | N bytes |
+--------+--------+------------+
public class CustomProtocolEncoder extends MessageToByteEncoder<CustomMessage> {
@Override
protected void encode(ChannelHandlerContext ctx,
CustomMessage msg,
ByteBuf out) {
// 获取数据
byte[] data = msg.getData();
byte type = msg.getType();
// 写入长度字段
out.writeInt(data.length);
// 写入类型字段
out.writeByte(type);
// 写入数据
out.writeBytes(data);
}
}
完整数据流:
// 创建消息对象
CustomMessage message = new CustomMessage();
message.setType((byte) 0x01);
message.setData("Hello".getBytes());
// 发送消息
ctx.write(message);
// encode() 执行过程:
// 1. data.length = 5
// 2. type = 0x01
// 3. data = [0x48, 0x65, 0x6C, 0x6C, 0x6F]
// 编码后的 ByteBuf:
索引: 0 1 2 3 4 5 6 7 8 9
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
数据: │ 00 │ 00 │ 00 │ 05 │ 01 │ 48 │ 65 │ 6C │ 6C │ 6F │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
└────────长度=5────────┘ └类型┘ └──────"Hello"──────┘
3.3 复杂示例:JSON 编码器
public class JsonEncoder extends MessageToByteEncoder<Object> {
private final ObjectMapper mapper = new ObjectMapper();
@Override
protected void encode(ChannelHandlerContext ctx,
Object msg,
ByteBuf out) throws Exception {
// 将对象序列化为JSON字符串
String json = mapper.writeValueAsString(msg);
// 转换为字节数组
byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
// 写入长度前缀(解决粘包问题)
out.writeInt(bytes.length);
// 写入JSON数据
out.writeBytes(bytes);
}
}
使用示例:
// 业务对象
User user = new User("Alice", 25);
// 发送
ctx.write(user);
// 编码过程:
// 1. 序列化: {"name":"Alice","age":25}
// 2. 字节数组: [0x7B, 0x22, 0x6E, ...]
// 3. 长度: 27
// 4. 最终输出: [长度4字节][JSON数据27字节]
四、MessageToMessageEncoder 深度解析
4.1 与 MessageToByteEncoder 的区别
MessageToByteEncoder: Java对象 → ByteBuf
MessageToMessageEncoder: Java对象A → Java对象B
4.2 典型应用场景
场景1:协议转换链
// 第一层:业务对象 → 协议对象
public class UserToProtocolEncoder extends MessageToMessageEncoder<User> {
@Override
protected void encode(ChannelHandlerContext ctx,
User user,
List<Object> out) {
ProtocolMessage msg = new ProtocolMessage();
msg.setType(MessageType.USER_INFO);
msg.setData(user.toBytes());
out.add(msg);
}
}
// 第二层:协议对象 → ByteBuf
public class ProtocolToByteEncoder extends MessageToByteEncoder<ProtocolMessage> {
@Override
protected void encode(ChannelHandlerContext ctx,
ProtocolMessage msg,
ByteBuf out) {
out.writeInt(msg.getType());
out.writeBytes(msg.getData());
}
}
// Pipeline 配置
pipeline.addLast(new UserToProtocolEncoder());
pipeline.addLast(new ProtocolToByteEncoder());
数据流:
User对象
↓ UserToProtocolEncoder
ProtocolMessage对象
↓ ProtocolToByteEncoder
ByteBuf
↓
网络传输
场景2:Integer → String 转换
public class IntegerToStringEncoder extends MessageToMessageEncoder<Integer> {
@Override
protected void encode(ChannelHandlerContext ctx,
Integer msg,
List<Object> out) {
// 将 Integer 转换为 String
out.add("Number: " + msg);
}
}
使用示例:
// Pipeline 配置
pipeline.addLast(new IntegerToStringEncoder());
pipeline.addLast(new StringEncoder()); // Netty内置
// 发送
ctx.write(123);
// 转换过程:
// 1. IntegerToStringEncoder: 123 → "Number: 123"
// 2. StringEncoder: "Number: 123" → ByteBuf
4.3 write() 方法源码详解
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
CodecOutputList out = null;
try {
// 步骤1:类型检查
if (acceptOutboundMessage(msg)) {
// 步骤2:创建输出列表
out = CodecOutputList.newInstance();
I cast = (I) msg;
try {
// 步骤3:编码(可能产生多个输出)
encode(ctx, cast, out);
} finally {
// 步骤4:释放原始消息
ReferenceCountUtil.release(cast);
}
// 步骤5:检查输出
if (out.isEmpty()) {
throw new EncoderException(
"must produce at least one message.");
}
} else {
// 类型不匹配,直接传递
ctx.write(msg, promise);
}
} finally {
if (out != null) {
// 步骤6:传递所有输出消息
final int sizeMinusOne = out.size() - 1;
if (sizeMinusOne == 0) {
// 只有一个输出
ctx.write(out.getUnsafe(0), promise);
} else if (sizeMinusOne > 0) {
// 多个输出
if (promise == ctx.voidPromise()) {
writeVoidPromise(ctx, out);
} else {
writePromiseCombiner(ctx, out, promise);
}
}
out.recycle();
}
}
}
4.4 一对多编码示例
public class MessageSplitEncoder extends MessageToMessageEncoder<LargeMessage> {
private static final int MAX_SIZE = 1024;
@Override
protected void encode(ChannelHandlerContext ctx,
LargeMessage msg,
List<Object> out) {
byte[] data = msg.getData();
// 将大消息拆分成多个小消息
for (int i = 0; i < data.length; i += MAX_SIZE) {
int length = Math.min(MAX_SIZE, data.length - i);
byte[] chunk = new byte[length];
System.arraycopy(data, i, chunk, 0, length);
SmallMessage small = new SmallMessage();
small.setData(chunk);
small.setSequence(i / MAX_SIZE);
out.add(small); // 添加多个输出
}
}
}
数据流演示:
输入: LargeMessage (3000字节)
encode() 执行:
拆分成3个 SmallMessage
- SmallMessage[0]: 1024字节
- SmallMessage[1]: 1024字节
- SmallMessage[2]: 952字节
输出: List<SmallMessage> (3个对象)
Pipeline 后续处理:
每个 SmallMessage 会被单独传递给下一个Handler
五、编码器的内存管理
5.1 自动释放机制
try {
encode(ctx, cast, out);
} finally {
// 关键:自动释放原始消息
ReferenceCountUtil.release(cast);
}
为什么要释放?
// 用户代码
CustomMessage msg = new CustomMessage();
msg.setData(new byte[1024]); // 分配内存
ctx.write(msg); // 传递给编码器
// 编码器内部:
// 1. 读取 msg 的数据
// 2. 写入 ByteBuf
// 3. 释放 msg(避免内存泄漏)
// 4. 传递 ByteBuf
5.2 何时需要 retain()?
public class AsyncEncoder extends MessageToByteEncoder<CustomMessage> {
private final ExecutorService executor = Executors.newFixedThreadPool(4);
@Override
protected void encode(ChannelHandlerContext ctx,
CustomMessage msg,
ByteBuf out) {
// ❌ 错误:异步处理会导致问题
executor.submit(() -> {
// 此时 msg 可能已经被释放!
byte[] data = msg.getData(); // 可能抛出异常
out.writeBytes(data);
});
}
}
正确做法:
// 方案1:在 encode() 中完成所有操作(推荐)
@Override
protected void encode(ChannelHandlerContext ctx,
CustomMessage msg,
ByteBuf out) {
// 同步处理,不需要 retain
byte[] data = msg.getData();
out.writeBytes(data);
}
// 方案2:如果必须异步,需要特殊处理
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
if (msg instanceof CustomMessage) {
CustomMessage custom = (CustomMessage) msg;
// 增加引用计数
ReferenceCountUtil.retain(custom);
executor.submit(() -> {
try {
ByteBuf buf = ctx.alloc().buffer();
byte[] data = custom.getData();
buf.writeBytes(data);
ctx.write(buf, promise);
} finally {
// 手动释放
ReferenceCountUtil.release(custom);
}
});
} else {
ctx.write(msg, promise);
}
}
六、编码器与解码器的配合使用
6.1 完整的编解码链
// 服务端 Pipeline
pipeline.addLast("decoder", new CustomProtocolDecoder()); // 入站
pipeline.addLast("encoder", new CustomProtocolEncoder()); // 出站
pipeline.addLast("handler", new BusinessHandler());
// 客户端 Pipeline
pipeline.addLast("encoder", new CustomProtocolEncoder()); // 出站
pipeline.addLast("decoder", new CustomProtocolDecoder()); // 入站
pipeline.addLast("handler", new ClientHandler());
数据流向:
客户端发送:
User对象
↓ CustomProtocolEncoder (出站)
ByteBuf
↓ 网络传输
ByteBuf
↓ CustomProtocolDecoder (入站)
User对象
↓ BusinessHandler
服务端响应:
Response对象
↓ CustomProtocolEncoder (出站)
ByteBuf
↓ 网络传输
ByteBuf
↓ CustomProtocolDecoder (入站)
Response对象
↓ ClientHandler
6.2 对称的编解码器实现
// 编码器
public class MessageEncoder extends MessageToByteEncoder<Message> {
@Override
protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) {
byte[] data = msg.getData();
out.writeInt(data.length); // 长度
out.writeByte(msg.getType()); // 类型
out.writeBytes(data); // 数据
}
}
// 解码器(对称实现)
public class MessageDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
if (in.readableBytes() < 5) return;
in.markReaderIndex();
int length = in.readInt(); // 读取长度
byte type = in.readByte(); // 读取类型
if (in.readableBytes() < length) {
in.resetReaderIndex();
return;
}
byte[] data = new byte[length];
in.readBytes(data); // 读取数据
Message msg = new Message();
msg.setType(type);
msg.setData(data);
out.add(msg);
}
}
七、常见问题和最佳实践
7.1 问题1:忘记写入数据
// ❌ 错误示例
@Override
protected void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) {
// 什么都不做
// out 是空的!
}
// 结果:
// buf.isReadable() = false
// 会发送 Unpooled.EMPTY_BUFFER
正确做法:确保写入数据
@Override
protected void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) {
out.writeInt(msg); // 必须写入数据
}
7.2 问题2:ByteBuf 泄漏
// ❌ 错误示例
@Override
protected void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) {
ByteBuf temp = ctx.alloc().buffer();
temp.writeInt(msg);
// 忘记释放 temp!
out.writeBytes(temp);
}
// ✅ 正确做法
@Override
protected void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) {
ByteBuf temp = ctx.alloc().buffer();
try {
temp.writeInt(msg);
out.writeBytes(temp);
} finally {
temp.release(); // 释放临时缓冲区
}
}
// ✅ 更好的做法:直接写入
@Override
protected void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) {
out.writeInt(msg); // 不需要临时缓冲区
}
7.3 问题3:编码器顺序错误
// ❌ 错误的 Pipeline 配置
pipeline.addLast(new StringEncoder()); // 期望 String
pipeline.addLast(new IntegerToStringEncoder()); // 输出 String
// 问题:IntegerToStringEncoder 在后面,
// 它的输出无法被 StringEncoder 处理
// ✅ 正确的配置
pipeline.addLast(new IntegerToStringEncoder()); // Integer → String
pipeline.addLast(new StringEncoder()); // String → ByteBuf
记住:出站Handler的执行顺序是从后往前!
// Pipeline: [H1] → [H2] → [H3] → [Tail]
ctx.write(msg);
// 执行顺序:H3 → H2 → H1 → 网络
八、Netty 内置编码器
8.1 常用编码器
// 1. 字符串编码器
pipeline.addLast(new StringEncoder(StandardCharsets.UTF_8));
// 2. Base64 编码器
pipeline.addLast(new Base64Encoder());
// 3. 长度字段编码器
pipeline.addLast(new LengthFieldPrepender(4)); // 添加4字节长度前缀
// 4. 对象序列化编码器
pipeline.addLast(new ObjectEncoder());
8.2 LengthFieldPrepender 详解
// 自动添加长度字段
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new StringEncoder());
// 发送
ctx.write("Hello");
// 编码过程:
// 1. StringEncoder: "Hello" → [0x48, 0x65, 0x6C, 0x6C, 0x6F]
// 2. LengthFieldPrepender: 添加长度 → [0x00, 0x00, 0x00, 0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F]
九、总结
9.1 编码器选择指南
| 场景 | 推荐编码器 | 原因 |
|---|---|---|
| 对象 → 字节 | MessageToByteEncoder | 直接编码为网络数据 |
| 对象 → 对象 | MessageToMessageEncoder | 协议转换、数据转换 |
| 字符串发送 | StringEncoder | Netty内置,开箱即用 |
| 自定义协议 | MessageToByteEncoder | 灵活控制字节格式 |
9.2 核心要点
- 编码器处理出站数据,继承
ChannelOutboundHandlerAdapter - 自动释放原始消息,避免内存泄漏
- preferDirect 控制内存类型,默认使用直接内存
- MessageToMessageEncoder 可以一对多,一个输入产生多个输出
- 注意 Pipeline 顺序,出站Handler从后往前执行
9.3 与解码器的对比
| 特性 | 解码器 | 编码器 |
|---|---|---|
| 数据方向 | 入站(Inbound) | 出站(Outbound) |
| 基类 | ChannelInboundHandlerAdapter | ChannelOutboundHandlerAdapter |
| 核心方法 | channelRead() | write() |
| 累积机制 | 有(cumulation) | 无 |
| 典型用途 | 字节 → 对象 | 对象 → 字节 |
9.4 学习建议
结合你正在学习的内容:
- 理解 ChannelPipeline 的双向传播机制
- 掌握 ByteBuf 的读写操作
- 实现一对完整的编解码器
- 调试观察 write() 方法的执行过程
希望这个详细解析能帮助你深入理解 Netty 编码器!
十、实战:完整的编解码器实现
10.1 自定义协议定义
协议格式:
+--------+--------+--------+------------+
| Magic | Length | Type | Data |
| 2 bytes| 4 bytes| 1 byte | N bytes |
+--------+--------+--------+------------+
Magic: 0xCAFE (魔数,用于识别协议)
Length: 数据长度(不包含头部)
Type: 消息类型
Data: 实际数据
10.2 消息对象
public class ProtocolMessage {
public static final short MAGIC = (short) 0xCAFE;
private byte type;
private byte[] data;
// 消息类型常量
public static final byte TYPE_REQUEST = 0x01;
public static final byte TYPE_RESPONSE = 0x02;
public static final byte TYPE_HEARTBEAT = 0x03;
// Getters and Setters
public byte getType() { return type; }
public void setType(byte type) { this.type = type; }
public byte[] getData() { return data; }
public void setData(byte[] data) { this.data = data; }
}
10.3 编码器实现
public class ProtocolMessageEncoder extends MessageToByteEncoder<ProtocolMessage> {
@Override
protected void encode(ChannelHandlerContext ctx,
ProtocolMessage msg,
ByteBuf out) throws Exception {
// 1. 写入魔数(2字节)
out.writeShort(ProtocolMessage.MAGIC);
// 2. 写入数据长度(4字节)
byte[] data = msg.getData();
int length = (data != null) ? data.length : 0;
out.writeInt(length);
// 3. 写入消息类型(1字节)
out.writeByte(msg.getType());
// 4. 写入数据
if (data != null && data.length > 0) {
out.writeBytes(data);
}
// 可选:添加日志
if (logger.isDebugEnabled()) {
logger.debug("Encoded message: type={}, length={}",
msg.getType(), length);
}
}
}
10.4 解码器实现
public class ProtocolMessageDecoder extends ByteToMessageDecoder {
private static final int HEADER_SIZE = 7; // 2 + 4 + 1
private static final int MAX_FRAME_LENGTH = 1024 * 1024; // 1MB
@Override
protected void decode(ChannelHandlerContext ctx,
ByteBuf in,
List<Object> out) throws Exception {
// 1. 检查是否有足够的字节读取头部
if (in.readableBytes() < HEADER_SIZE) {
return;
}
// 2. 标记读位置
in.markReaderIndex();
// 3. 读取并验证魔数
short magic = in.readShort();
if (magic != ProtocolMessage.MAGIC) {
// 魔数不匹配,可能是错误的协议
in.resetReaderIndex();
throw new DecoderException("Invalid magic number: 0x"
+ Integer.toHexString(magic));
}
// 4. 读取长度
int length = in.readInt();
// 5. 验证长度
if (length < 0 || length > MAX_FRAME_LENGTH) {
throw new DecoderException("Invalid frame length: " + length);
}
// 6. 读取类型
byte type = in.readByte();
// 7. 检查数据是否完整
if (in.readableBytes() < length) {
// 数据不完整,重置读位置
in.resetReaderIndex();
return;
}
// 8. 读取数据
byte[] data = null;
if (length > 0) {
data = new byte[length];
in.readBytes(data);
}
// 9. 创建消息对象
ProtocolMessage message = new ProtocolMessage();
message.setType(type);
message.setData(data);
// 10. 添加到输出列表
out.add(message);
// 可选:添加日志
if (logger.isDebugEnabled()) {
logger.debug("Decoded message: type={}, length={}", type, length);
}
}
}
10.5 完整的数据流演示
// 发送端
ProtocolMessage request = new ProtocolMessage();
request.setType(ProtocolMessage.TYPE_REQUEST);
request.setData("Hello Server".getBytes(StandardCharsets.UTF_8));
ctx.writeAndFlush(request);
// 编码过程:
// ┌──────┬──────────┬──────┬─────────────────────────┐
// │ CAFE │ 0000000C │ 01 │ Hello Server │
// └──────┴──────────┴──────┴─────────────────────────┘
// 2字节 4字节 1字节 12字节
// 网络传输(可能分包):
// 第一次接收: [CA, FE, 00, 00, 00, 0C, 01, 48, 65]
// 第二次接收: [6C, 6C, 6F, 20, 53, 65, 72, 76, 65, 72]
// 解码过程:
// 第一次 decode(): 数据不完整,return
// 第二次 decode(): 数据完整,解码成功
// 输出: ProtocolMessage{type=1, data="Hello Server"}
10.6 Pipeline 配置
// 服务端
public class ServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
// 解码器(入站)
pipeline.addLast("decoder", new ProtocolMessageDecoder());
// 编码器(出站)
pipeline.addLast("encoder", new ProtocolMessageEncoder());
// 业务Handler
pipeline.addLast("handler", new ServerHandler());
}
}
// 客户端
public class ClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
// 编码器(出站)
pipeline.addLast("encoder", new ProtocolMessageEncoder());
// 解码器(入站)
pipeline.addLast("decoder", new ProtocolMessageDecoder());
// 业务Handler
pipeline.addLast("handler", new ClientHandler());
}
}
10.7 业务Handler示例
public class ServerHandler extends SimpleChannelInboundHandler<ProtocolMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ProtocolMessage msg) {
System.out.println("Received: type=" + msg.getType()
+ ", data=" + new String(msg.getData()));
// 根据消息类型处理
switch (msg.getType()) {
case ProtocolMessage.TYPE_REQUEST:
handleRequest(ctx, msg);
break;
case ProtocolMessage.TYPE_HEARTBEAT:
handleHeartbeat(ctx);
break;
default:
System.err.println("Unknown message type: " + msg.getType());
}
}
private void handleRequest(ChannelHandlerContext ctx, ProtocolMessage msg) {
// 处理请求
String request = new String(msg.getData(), StandardCharsets.UTF_8);
String response = "Echo: " + request;
// 发送响应
ProtocolMessage responseMsg = new ProtocolMessage();
responseMsg.setType(ProtocolMessage.TYPE_RESPONSE);
responseMsg.setData(response.getBytes(StandardCharsets.UTF_8));
ctx.writeAndFlush(responseMsg);
}
private void handleHeartbeat(ChannelHandlerContext ctx) {
// 响应心跳
ProtocolMessage heartbeat = new ProtocolMessage();
heartbeat.setType(ProtocolMessage.TYPE_HEARTBEAT);
heartbeat.setData(new byte[0]);
ctx.writeAndFlush(heartbeat);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
10.8 测试代码
public class ProtocolTest {
@Test
public void testEncodeDecode() throws Exception {
// 创建消息
ProtocolMessage original = new ProtocolMessage();
original.setType(ProtocolMessage.TYPE_REQUEST);
original.setData("Test Message".getBytes(StandardCharsets.UTF_8));
// 编码
EmbeddedChannel channel = new EmbeddedChannel(
new ProtocolMessageEncoder(),
new ProtocolMessageDecoder()
);
// 写入消息(触发编码)
assertTrue(channel.writeOutbound(original));
// 读取编码后的 ByteBuf
ByteBuf encoded = channel.readOutbound();
assertNotNull(encoded);
// 验证编码格式
assertEquals(ProtocolMessage.MAGIC, encoded.readShort()); // 魔数
assertEquals(12, encoded.readInt()); // 长度
assertEquals(ProtocolMessage.TYPE_REQUEST, encoded.readByte()); // 类型
// 重置读位置
encoded.resetReaderIndex();
// 写入编码数据(触发解码)
assertTrue(channel.writeInbound(encoded));
// 读取解码后的消息
ProtocolMessage decoded = channel.readInbound();
assertNotNull(decoded);
// 验证解码结果
assertEquals(original.getType(), decoded.getType());
assertArrayEquals(original.getData(), decoded.getData());
// 清理
assertFalse(channel.finish());
}
@Test
public void testFragmentation() throws Exception {
// 测试分包场景
EmbeddedChannel channel = new EmbeddedChannel(
new ProtocolMessageDecoder()
);
// 创建完整的消息字节
ByteBuf fullMessage = Unpooled.buffer();
fullMessage.writeShort(ProtocolMessage.MAGIC);
fullMessage.writeInt(5);
fullMessage.writeByte(ProtocolMessage.TYPE_REQUEST);
fullMessage.writeBytes("Hello".getBytes());
// 分成两部分发送
ByteBuf part1 = fullMessage.readSlice(10); // 头部 + 部分数据
ByteBuf part2 = fullMessage.readSlice(fullMessage.readableBytes());
// 发送第一部分(不完整)
assertFalse(channel.writeInbound(part1.retain()));
assertNull(channel.readInbound()); // 没有输出
// 发送第二部分(完整)
assertTrue(channel.writeInbound(part2.retain()));
// 读取解码后的消息
ProtocolMessage decoded = channel.readInbound();
assertNotNull(decoded);
assertEquals("Hello", new String(decoded.getData()));
// 清理
fullMessage.release();
assertFalse(channel.finish());
}
}
十一、性能优化技巧
11.1 使用对象池
public class OptimizedEncoder extends MessageToByteEncoder<ProtocolMessage> {
// 使用 Recycler 对象池
private static final Recycler<EncodeContext> RECYCLER = new Recycler<EncodeContext>() {
@Override
protected EncodeContext newObject(Handle<EncodeContext> handle) {
return new EncodeContext(handle);
}
};
@Override
protected void encode(ChannelHandlerContext ctx,
ProtocolMessage msg,
ByteBuf out) {
EncodeContext context = RECYCLER.get();
try {
context.encode(msg, out);
} finally {
context.recycle();
}
}
private static class EncodeContext {
private final Recycler.Handle<EncodeContext> handle;
EncodeContext(Recycler.Handle<EncodeContext> handle) {
this.handle = handle;
}
void encode(ProtocolMessage msg, ByteBuf out) {
// 编码逻辑
out.writeShort(ProtocolMessage.MAGIC);
// ...
}
void recycle() {
handle.recycle(this);
}
}
}
11.2 预估 ByteBuf 大小
@Override
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx,
ProtocolMessage msg,
boolean preferDirect) {
// 预估大小:头部 + 数据长度
int estimatedSize = 7 + (msg.getData() != null ? msg.getData().length : 0);
return preferDirect ?
ctx.alloc().ioBuffer(estimatedSize) :
ctx.alloc().heapBuffer(estimatedSize);
}
11.3 批量编码
public class BatchEncoder extends MessageToMessageEncoder<List<ProtocolMessage>> {
@Override
protected void encode(ChannelHandlerContext ctx,
List<ProtocolMessage> msgs,
List<Object> out) {
// 计算总大小
int totalSize = 0;
for (ProtocolMessage msg : msgs) {
totalSize += 7 + (msg.getData() != null ? msg.getData().length : 0);
}
// 一次性分配
ByteBuf buf = ctx.alloc().buffer(totalSize);
// 批量编码
for (ProtocolMessage msg : msgs) {
buf.writeShort(ProtocolMessage.MAGIC);
buf.writeInt(msg.getData().length);
buf.writeByte(msg.getType());
buf.writeBytes(msg.getData());
}
out.add(buf);
}
}
十二、调试技巧
12.1 添加日志Handler
public class LoggingEncoder extends MessageToByteEncoder<ProtocolMessage> {
private static final Logger logger = LoggerFactory.getLogger(LoggingEncoder.class);
@Override
protected void encode(ChannelHandlerContext ctx,
ProtocolMessage msg,
ByteBuf out) {
int startIndex = out.writerIndex();
// 编码
out.writeShort(ProtocolMessage.MAGIC);
out.writeInt(msg.getData().length);
out.writeByte(msg.getType());
out.writeBytes(msg.getData());
int endIndex = out.writerIndex();
int encodedBytes = endIndex - startIndex;
// 记录日志
if (logger.isDebugEnabled()) {
logger.debug("Encoded {} bytes: type={}, dataLength={}",
encodedBytes, msg.getType(), msg.getData().length);
// 打印十六进制
if (logger.isTraceEnabled()) {
logger.trace("Hex dump:\n{}",
ByteBufUtil.hexDump(out, startIndex, encodedBytes));
}
}
}
}
12.2 使用 EmbeddedChannel 测试
@Test
public void debugEncodeDecode() {
// 创建测试通道
EmbeddedChannel channel = new EmbeddedChannel(
new LoggingHandler(LogLevel.DEBUG), // 添加日志
new ProtocolMessageEncoder(),
new ProtocolMessageDecoder()
);
// 创建消息
ProtocolMessage msg = new ProtocolMessage();
msg.setType(ProtocolMessage.TYPE_REQUEST);
msg.setData("Debug Test".getBytes());
// 写入并观察日志
channel.writeOutbound(msg);
// 读取编码结果
ByteBuf encoded = channel.readOutbound();
System.out.println("Encoded bytes: " + encoded.readableBytes());
System.out.println("Hex: " + ByteBufUtil.hexDump(encoded));
// 清理
encoded.release();
channel.finish();
}
635



被折叠的 条评论
为什么被折叠?



