Netty:常见 ChannelHandler 分类、作用与 WebSocket 处理机制
一、ChannelHandler 是什么?
ChannelHandler 是 Netty 中负责处理网络事件和消息的组件。
每个 Channel 都有一个对应的 ChannelPipeline,其中可以添加多个 Handler,组成消息处理链。
Netty Handler 主要分为两类:
ChannelInboundHandler:处理入站事件,例如连接建立、读取客户端消息、连接断开。ChannelOutboundHandler:处理出站操作,例如发送消息、连接远程地址、关闭连接。
1. 入站和出站传播方向
客户端发送消息
↓
Pipeline Head
↓
InboundHandler A
↓
InboundHandler B
↓
InboundHandler C
↓
Pipeline Tail入站事件通常从 Head 向 Tail 传播。
出站操作则通常从 Tail 向 Head 传播。
需要注意,实际从哪个节点开始传播与调用方式有关:
channel.writeAndFlush():从 Pipeline 尾部开始寻找出站 Handler。ctx.writeAndFlush():从当前 Handler 所在位置向前寻找出站 Handler。ctx.fireChannelRead():从当前位置向后传播入站消息。
二、常见 Handler 分类
1. 入站 Handler
2. 出站 Handler
注意:ByteToMessageEncoder 并不是 Netty 常用的标准编码器类,通常应该使用 MessageToByteEncoder。
3. HTTP 与 WebSocket Handler
其中:
IdleStateHandler 本身不是专门的 WebSocket 心跳 Handler,而是通用的空闲检测 Handler,可以配合心跳业务使用。
三、ChannelInboundHandlerAdapter 与 SimpleChannelInboundHandler 的区别
两者都能处理入站事件,主要区别在于消息释放方式。
1. ChannelInboundHandlerAdapter
public class MyHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
System.out.println("收到消息:" + msg);
} finally {
ReferenceCountUtil.release(msg);
}
}
}特点:
不会自动释放消息。
可以手动决定消息是否继续向后传递。
适合需要自行管理消息生命周期的场景。
注意:上面的代码表示当前 Handler 消费了消息,不再向下传播。
如果使用:
ctx.fireChannelRead(msg);表示将消息交给下一个 Handler,此时不能再无条件释放同一个消息,否则可能导致后续 Handler 读取已经释放的数据。
2. SimpleChannelInboundHandler
public class MyHandler
extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(
ChannelHandlerContext ctx,
String msg) {
System.out.println("收到消息:" + msg);
}
}特点:
继承自
ChannelInboundHandlerAdapter。根据泛型匹配消息类型。
默认自动释放处理完成的匹配消息。
未匹配的消息会继续传播。
其核心处理逻辑可以简化理解为:
if (acceptInboundMessage(msg)) {
try {
channelRead0(ctx, (I) msg);
} finally {
ReferenceCountUtil.release(msg);
}
} else {
ctx.fireChannelRead(msg);
}这里 ReferenceCountUtil.release() 对具有引用计数的消息(例如 ByteBuf)非常重要。
对于普通 String 对象,通常没有实际释放操作。
3. 二者如何选择?
重点:SimpleChannelInboundHandler 不适合直接保存需要异步使用的引用计数消息。
例如异步处理 ByteBuf,需要先复制内容或者通过 retain() 保留引用,并确保最终释放。
四、WebSocket 是什么 Handler?
WebSocket 不是 Handler,而是一种基于 TCP 的全双工通信协议。
Netty 使用:
WebSocketServerProtocolHandler来处理 WebSocket 协议。
它主要负责:
处理 HTTP Upgrade 握手。
将 HTTP 通信升级为 WebSocket。
处理 Close 控制帧。
根据配置处理 Ping、Pong 控制帧。
触发 WebSocket 握手相关事件。
WebSocket 业务数据需要由自定义 Handler 处理。
1. WebSocket 建立连接过程
客户端建立 TCP 连接
↓
发送 HTTP Upgrade 请求
↓
HttpServerCodec
↓
HttpObjectAggregator
↓
WebSocketServerProtocolHandler
↓
服务器返回 101 Switching Protocols
↓
WebSocket 握手成功
↓
双方通过 WebSocket Frame 通信这里需要区分:
TCP 连接建立:对应底层网络连接。
WebSocket 握手成功:表示 HTTP Upgrade 已经完成。
channelActive() 不代表 WebSocket 握手成功。
如果需要在握手完成后执行设备注册、认证等操作,可以监听 WebSocketServerProtocolHandler.HandshakeComplete 用户事件。
五、WebSocketFrame 类型
WebSocket 通过 Frame 传输消息。
注意:这些都是消息类型,而不是 Handler。
六、Netty WebSocket Pipeline 示例
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
// HTTP 编解码
pipeline.addLast(new HttpServerCodec());
// 聚合 HTTP 请求
pipeline.addLast(new HttpObjectAggregator(65536));
// WebSocket 协议处理
pipeline.addLast(
new WebSocketServerProtocolHandler("/websocket")
);
// WebSocket 业务处理
pipeline.addLast(new WebSocketBusinessHandler());
}业务 Handler:
public class WebSocketBusinessHandler
extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(
ChannelHandlerContext ctx,
TextWebSocketFrame frame) {
String message = frame.text();
System.out.println("收到消息:" + message);
ctx.writeAndFlush(
new TextWebSocketFrame("服务端收到:" + message)
);
}
}执行过程:
客户端发送 WebSocket 文本消息
↓
WebSocket 协议解码
↓
TextWebSocketFrame
↓
WebSocketBusinessHandler
↓
channelRead0()
↓
执行业务逻辑
↓
ctx.writeAndFlush()
↓
编码成 WebSocket 帧
↓
发送给客户端这里有两个重要细节:
WebSocket 的帧解码和编码由协议相关 Handler 处理,并不是
HttpServerCodec直接解析 WebSocket 消息。SimpleChannelInboundHandler<TextWebSocketFrame>只处理文本帧。如果要处理二进制帧,需要增加对应 Handler,或改为处理WebSocketFrame基类。
七、常见问题与答案
1. ChannelInboundHandlerAdapter 和 SimpleChannelInboundHandler 有什么区别?
主要区别是消息释放机制。
前者不自动释放消息;后者默认自动释放匹配且处理完成的消息。
2. 为什么 SimpleChannelInboundHandler 有泛型?
用于指定当前 Handler 接收的消息类型。
例如:
SimpleChannelInboundHandler<TextWebSocketFrame>表示这个 Handler 专门处理 WebSocket 文本帧。
3. WebSocketServerProtocolHandler 和 SimpleChannelInboundHandler 是什么关系?
前者负责 WebSocket 协议处理,后者可以实现具体业务消息处理。
它们可以同时加入同一个 Pipeline。
4. WebSocket 为什么需要 HttpServerCodec?
因为基于 HTTP/1.1 Upgrade 的 WebSocket 握手最初是一个 HTTP 请求,完成升级后才使用 WebSocket 帧传输消息。
5. WebSocketServerProtocolHandler 会处理所有消息吗?
不会。
它主要处理 WebSocket 协议相关流程,例如握手和控制帧。普通文本和二进制数据帧仍然需要交给业务 Handler。
6. TCP 粘包、半包应该使用什么 Handler?
根据协议选择:
固定长度:
FixedLengthFrameDecoder分隔符:
DelimiterBasedFrameDecoder长度字段:
LengthFieldBasedFrameDecoder完全自定义协议:
ByteToMessageDecoder
7. ctx.writeAndFlush() 和 channel.writeAndFlush() 有什么区别?
主要区别是出站传播的起点不同。
ctx.writeAndFlush() 从当前 Handler 位置向前寻找出站 Handler。
channel.writeAndFlush() 从 Pipeline 尾部开始传播出站操作。
如果 Pipeline 中存在多个编码器,这个区别可能决定哪些编码器会被执行。
八、重点总结
ChannelHandler是 Netty 处理网络事件和消息的核心组件。ChannelInboundHandler负责入站事件,ChannelOutboundHandler负责出站操作。ChannelInboundHandlerAdapter不自动释放消息,需要明确消息所有权。SimpleChannelInboundHandler默认自动释放匹配并处理完成的引用计数消息。ByteToMessageDecoder负责解码,MessageToByteEncoder负责编码。WebSocket是通信协议,WebSocketServerProtocolHandler是协议处理器。TextWebSocketFrame、BinaryWebSocketFrame等是消息类型,不是 Handler。WebSocket 先完成 HTTP Upgrade 握手,再通过 WebSocket Frame 通信。
channelActive()代表底层 Channel 已激活,不代表 WebSocket 握手成功。生产环境需要特别关注 Handler 的执行顺序、引用计数释放以及消息的异步处理。
一句话概括:Netty 使用不同的 Handler 分别完成协议解码、协议处理、业务处理和消息编码,多个 Handler 通过 ChannelPipeline 组成完整的网络通信处理链。
