3
0
0

Netty:常见 ChannelHandler 分类、作用与 WebSocket 处理机制

文章摘要
|

一、ChannelHandler 是什么?

ChannelHandler 是 Netty 中负责处理网络事件和消息的组件。

每个 Channel 都有一个对应的 ChannelPipeline,其中可以添加多个 Handler,组成消息处理链。

Netty Handler 主要分为两类:

  1. ChannelInboundHandler:处理入站事件,例如连接建立、读取客户端消息、连接断开。

  2. 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

Handler

主要作用

ChannelInboundHandlerAdapter

基础入站事件处理

SimpleChannelInboundHandler<T>

自动释放匹配的入站消息

ByteToMessageDecoder

将字节流解码成业务消息

MessageToMessageDecoder

将一种消息对象转换为另一种

ReplayingDecoder

简化半包处理,但存在一定性能开销

LengthFieldBasedFrameDecoder

根据长度字段解决 TCP 粘包、半包

DelimiterBasedFrameDecoder

根据分隔符拆分消息

2. 出站 Handler

Handler

主要作用

ChannelOutboundHandlerAdapter

基础出站操作处理

MessageToByteEncoder

将业务对象编码为 ByteBuf

MessageToMessageEncoder

将一种消息转换为另一种消息

LengthFieldPrepender

发送数据前添加长度字段

注意:ByteToMessageEncoder 并不是 Netty 常用的标准编码器类,通常应该使用 MessageToByteEncoder。

3. HTTP 与 WebSocket Handler

Handler

主要作用

HttpServerCodec

HTTP 请求解码、HTTP 响应编码

HttpObjectAggregator

将分段 HTTP 消息聚合

WebSocketServerProtocolHandler

处理 WebSocket 握手及控制帧

WebSocketFrameAggregator

聚合 WebSocket 分片消息

IdleStateHandler

检测读空闲、写空闲和全部空闲

ChunkedWriteHandler

支持大数据块的分块写出

其中:

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

处理连接生命周期事件

ChannelInboundHandlerAdapter

需要将消息交给其他 Handler

ChannelInboundHandlerAdapter

自定义 TCP 协议解码

ByteToMessageDecoder

重点:SimpleChannelInboundHandler 不适合直接保存需要异步使用的引用计数消息。

例如异步处理 ByteBuf,需要先复制内容或者通过 retain() 保留引用,并确保最终释放。

四、WebSocket 是什么 Handler?

WebSocket 不是 Handler,而是一种基于 TCP 的全双工通信协议。

Netty 使用:

WebSocketServerProtocolHandler

来处理 WebSocket 协议。

它主要负责:

  1. 处理 HTTP Upgrade 握手。

  2. 将 HTTP 通信升级为 WebSocket。

  3. 处理 Close 控制帧。

  4. 根据配置处理 Ping、Pong 控制帧。

  5. 触发 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 传输消息。

Frame

作用

TextWebSocketFrame

文本帧

BinaryWebSocketFrame

二进制帧

PingWebSocketFrame

Ping 控制帧

PongWebSocketFrame

Pong 控制帧

CloseWebSocketFrame

关闭连接帧

ContinuationWebSocketFrame

分片消息的后续帧

注意:这些都是消息类型,而不是 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 帧
         ↓
发送给客户端

这里有两个重要细节:

  1. WebSocket 的帧解码和编码由协议相关 Handler 处理,并不是 HttpServerCodec 直接解析 WebSocket 消息。

  2. 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 中存在多个编码器,这个区别可能决定哪些编码器会被执行。

八、重点总结

  1. ChannelHandler 是 Netty 处理网络事件和消息的核心组件。

  2. ChannelInboundHandler 负责入站事件,ChannelOutboundHandler 负责出站操作。

  3. ChannelInboundHandlerAdapter 不自动释放消息,需要明确消息所有权。

  4. SimpleChannelInboundHandler 默认自动释放匹配并处理完成的引用计数消息。

  5. ByteToMessageDecoder 负责解码,MessageToByteEncoder 负责编码。

  6. WebSocket 是通信协议,WebSocketServerProtocolHandler 是协议处理器。

  7. TextWebSocketFrame、BinaryWebSocketFrame 等是消息类型,不是 Handler。

  8. WebSocket 先完成 HTTP Upgrade 握手,再通过 WebSocket Frame 通信。

  9. channelActive() 代表底层 Channel 已激活,不代表 WebSocket 握手成功。

  10. 生产环境需要特别关注 Handler 的执行顺序、引用计数释放以及消息的异步处理。

一句话概括:Netty 使用不同的 Handler 分别完成协议解码、协议处理、业务处理和消息编码,多个 Handler 通过 ChannelPipeline 组成完整的网络通信处理链。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!

评论