目录
1、概念
2、Netty提供的编解码器类型
2.1 解码器
2.1.1 ByteToMessageDecoder
2.1.2 ReplayingDecoder
2.1.3 MessageToMessageDecoder
2.2 编码器
2.2.1 MessageToByteEncoder
2.2.2 MessageToMessageEncoder
2.3 编解码器
2.3.1 ByteToMessageCodec
2.3.2 MessageToMessageCodec
2.3.3 CombinedChannelDuplexHandler
3、使用注意事项
4、示例:一个简单的字符串编解码器
1、概念
Netty 的编解码器是其网络编程框架中的核心组件,负责处理网络通信中最基本也最关键的字节与消息对象之间的相互转换。
核心概念:
-
解码器: 将接收到的原始字节流 (
ByteBuf
) 转换成有意义的应用程序级消息对象。这个过程发生在数据入站时(从网络到应用)。 -
编码器: 将应用程序级的消息对象转换成字节流 (
ByteBuf
),以便通过网络发送。这个过程发生在数据出站时(从应用到网络)。 -
编解码器: 同时实现了编码器和解码器功能的组件。
解决的问题:
-
解码: 解决如何从连续的、可能被 TCP 粘包/拆包的字节流中,正确地切割、识别并还原出发送方发送的一个个完整消息。
-
编码: 解决如何将结构化的消息对象高效地、符合协议规范地序列化成字节序列,以便发送。
2、Netty提供的编解码器类型
2.1 解码器
Netty 的解码器都实现了 ChannelInboundHandler
接口,处理入站数据。需要将解码器放在ChannelPipeline中。常见的抽象基类和实现:
2.1.1 ByteToMessageDecoder
-
功能: 将入站的
ByteBuf
累积到内部的缓冲区中,直到有足够的数据可以解码成一个完整的消息对象。需要检查缓冲区是否有足够的字节。 -
关键方法:
decode(ChannelHandlerContext ctx, ByteBuf in, List out)
-
in
: 当前累积的、可读的输入ByteBuf
。 -
out
: 用于存放解码成功的完整消息对象的列表。每添加一个对象,就表示成功解码了一条消息。
-
-
职责: 开发者继承此类,实现
decode
方法:-
检查
in
中是否有足够的数据构成一个完整消息。 -
如果足够,从
in
中读取数据,构造出消息对象。 -
将构造好的消息对象添加到
out
列表中。 -
重复步骤 1-3 直到
in
中不再有足够的数据构成完整消息。
-
-
内部缓冲: 它会自动管理累积的字节,处理粘包问题。当
in
被读取后,已读取的字节会被丢弃,保留未读取的字节供下次解码。 -
示例:
LineBasedFrameDecoder
(基于换行符拆包),FixedLengthFrameDecoder
(固定长度拆包),LengthFieldBasedFrameDecoder
(基于长度字段拆包 - 极其重要和常用)。
2.1.2 ReplayingDecoder
-
特点: 简化了解码逻辑。它假设在调用
decode
方法时,ByteBuf
in
中总是有足够的数据可供读取下一个需要的字段。如果数据不足,它会抛出特殊异常(由框架捕获),并等待更多数据到来后重试解码。 -
优点: 代码更简洁,不需要手动检查每个字段是否有足够数据。
-
缺点: 性能可能略低于
ByteToMessageDecoder
(因为需要异常机制),且in
的某些操作受限(如readableBytes()
),不是所有的ByteBuff都支持。 -
适用: 协议简单或对极致性能要求不苛刻的场景。
2.1.3 MessageToMessageDecoder
-
核心: 将一种消息对象解码成另一种消息对象。
-
功能: 用于消息处理管道中的消息转换阶段。输入和输出都是对象,不再是原始的
ByteBuf
。 -
关键方法:
decode(ChannelHandlerContext ctx, I msg, List out)
-
msg
: 输入的上一个 Handler 传递下来的消息对象(类型I
)。 -
out
: 存放转换后的消息对象(类型O
)的列表。
-
-
示例: 将
ByteBuf
解码成的String
对象再解码成自定义的LoginRequest
对象;将 Protobuf 的ByteBuf
解码成生成的MessageLite
对象。
2.2 编码器
Netty 的编码器都实现了 ChannelOutboundHandler
接口,处理出站数据。常见的抽象基类和实现:
2.2.1 MessageToByteEncoder
-
功能: 将应用程序的消息对象编码成字节流 (
ByteBuf
)。 -
关键方法:
encode(ChannelHandlerContext ctx, I msg, ByteBuf out)
-
msg
: 要编码的消息对象(类型I
)。 -
out
: 用于写入编码后字节的目标ByteBuf
。
-
-
职责: 开发者继承此类,实现
encode
方法:-
将
msg
对象包含的信息(如字段值)按照协议规范写入到out
ByteBuf
中。 -
通常需要处理长度字段(如果需要)、序列化等。
-
-
示例: 将
String
编码成ByteBuf
(通常使用ctx.alloc().buffer()
创建并写入字节),将自定义的LoginResponse
对象编码成协议规定的字节格式。
2.2.2 MessageToMessageEncoder
-
核心: 将一种消息对象编码成另一种消息对象。
-
功能: 用于消息处理管道中的消息转换阶段,输出对象通常最终会被下游的
MessageToByteEncoder
处理。 -
关键方法:
encode(ChannelHandlerContext ctx, I msg, List out)
-
msg
: 要转换的消息对象(类型I
)。 -
out
: 存放转换后的消息对象(类型O
)的列表。
-
-
示例: 将自定义的
LoginResponse
对象转换成 Protobuf 生成的MessageLite
对象;将String
转换成包含该字符串的TextWebSocketFrame
。
2.3 编解码器
编码解码器: 同时具有编码与解码功能,特点同时实现了ChannelInboundHandler和ChannelOutboundHandler接口,因此在数据输入和输出时都能进行处理。Netty提供提供了一个ChannelDuplexHandler适配器类,编码解码器的抽象基类。
2.3.1 ByteToMessageCodec
结合了 ByteToMessageDecoder
和 MessageToByteEncoder
。
2.3.2 MessageToMessageCodec
结合了 MessageToMessageDecoder
和 MessageToMessageEncoder
。
2.3.3 CombinedChannelDuplexHandler
一个更灵活的通用组合方式,允许你将任意一个解码器 (ChannelInboundHandler
) 和任意一个编码器 (ChannelOutboundHandler
) 组合在一起,作为一个编解码器添加到 pipeline 中。这是推荐的方式来组合独立的编解码逻辑,避免多重继承可能带来的问题。
public class MyCodec extends CombinedChannelDuplexHandler<MyDecoder, MyEncoder> {public MyCodec() {super(new MyDecoder(), new MyEncoder());}
}
3、使用注意事项
-
处理 TCP 粘包/拆包: 这是解码器的核心职责之一。务必使用
ByteToMessageDecoder
或其子类(如LengthFieldBasedFrameDecoder
)来自动累积和拆包。不要试图在channelRead
中直接处理原始ByteBuf
来解析完整消息。 -
使用 LengthFieldBasedFrameDecoder: 对于自定义二进制协议,这是最常用、最可靠的拆包解码器。它通过协议头中明确指定的长度字段来确定一个完整帧的边界,完美解决粘包拆包问题。
-
资源管理:
ByteBuf
是 Netty 管理的内存。在编码器encode
方法中创建的ByteBuf
,Netty 框架会在写入完成后负责释放。在解码器decode
方法中,不要释放传入的ByteBuf
in
,ByteToMessageDecoder
会自动管理其生命周期。处理out
列表中的对象时,确保它们被正确传递下去或被释放(如果需要)。遵循 Netty 的引用计数规则。 -
可重用性: 编解码器通常是线程安全的和无状态的,可以被多个 Channel 共享(添加时使用
@Sharable
注解并确保确实无状态)。 -
Pipeline 顺序:
-
解码器 (
ChannelInboundHandler
) 应该添加在靠近SocketChannel
的一端(Pipeline 的前端)。 -
编码器 (
ChannelOutboundHandler
) 应该添加在靠近业务逻辑 Handler 的一端(Pipeline 的后端)。 -
一个典型的 Pipeline 顺序可能是:
LengthFieldBasedFrameDecoder
->MyProtocolDecoder
(ByteToMessageDecoder
) ->MyBusinessLogicHandler
->MyProtocolEncoder
(MessageToByteEncoder
) ->...
(其他出站处理器如日志、加密)。
-
-
利用现有实现: 优先使用 Netty 内置的编解码器(如 HTTP、WebSocket、Protobuf、Marshalling、Base64 等),它们经过充分测试和优化。
-
性能: 编解码通常是性能关键路径。避免在编解码方法中进行阻塞操作或创建过多临时对象。考虑使用对象池(如 Recycler)复用消息对象。
4、示例:一个简单的字符串编解码器
// 编码器 (出站:Object -> ByteBuf)
@Sharable
public class StringEncoder extends MessageToByteEncoder<String> {@Overrideprotected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {// 将字符串写入ByteBuf (使用UTF-8编码)out.writeCharSequence(msg, StandardCharsets.UTF_8);}
}// 解码器 (入站:ByteBuf -> String)
@Sharable
public class StringDecoder extends ByteToMessageDecoder {@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {// 简单示例:假设每个ByteBuf都包含一个完整的UTF-8字符串// 实际中需要处理粘包拆包!这里仅作演示。out.add(in.toString(StandardCharsets.UTF_8));in.readerIndex(in.writerIndex()); // 标记所有字节已读 (仅用于此简单示例)}
}// 或者使用更健壮的 LineBasedFrameDecoder + StringDecoder 组合来处理行分隔的消息
public class ServerInitializer extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline p = ch.pipeline();// 入站 Pipeline (从网络到应用)p.addLast(new LineBasedFrameDecoder(1024)); // 按行拆包,最大行长度1024p.addLast(new StringDecoder(StandardCharsets.UTF_8)); // 将一行字节转成Stringp.addLast(new MyStringHandler()); // 业务逻辑处理器,处理String对象// 出站 Pipeline (从应用到网络)p.addLast(new StringEncoder(StandardCharsets.UTF_8)); // 将String转成字节}
}