Netty学习笔记03-ChannelHandler和ChannelPipeline

本文深入探讨了Netty框架中的ChannelPipeline和ChannelHandler机制,详细解释了这些组件如何协同工作来处理I/O事件,包括入站和出站事件的处理流程、异常处理策略以及如何通过配置不同的ChannelHandler来定制业务逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

netty权威指南学习笔记
netty实战学习笔记

Netty的ChannelPipeline和ChannelHandler机制类似于Servlet和Filter过滤链.Netty将Channel的数据管道抽象为ChannelPipeline,消息在ChannelPipeline中流动和传递。ChannelPipeline持有 I/O事件 拦截器ChannelHandler的链表,由ChannelHandler对I/O事件进行拦截和处理,可以方便的通过新增和删除ChannelHandler来实现对不同业务逻辑定制。

ChannelHandler

ChannelHandler类似于J2EE中的Filter,负责对I/O事件进行拦截,它可以选择性的拦截和处理自己感兴趣的事件,也可以透传和终止事件的传输
基于ChannelHandler接口,用户可以方便地进行业务逻辑定制,例如日志打印,统一封装异常信息,性能统计和消息编辑码等。
ChannelHandler支持注解,目前注解有两种:

  • Sharable: 多个Pipeline公用一个ChannelHandler。
  • Skip: 被Skip注解的方法不会被调用,直接被忽略。
@ChannelHandler.Sharable
public class MyChannelHandler extends ChannelHandlerAdapter {

    @Skip
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        super.handlerAdded(ctx);
    }
}

ChannelHandler 的生命周期
这里写图片描述

Channel的类结构
这里写图片描述

这里写图片描述

Netty4.x 升级到到5.x,5.x简化处理器层次:
- ChannelInboundHandler和ChannelOutboundHandler被删除,整合为ChannelHandler。ChannelHandler现在包含输入和输出的处理方法。
- ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter被废弃(@Deprecated),由ChannelHandlerAdapter代替。

为了便于理解,先学习4.x的类结构,Netty 定义了下面两个重要的 ChannelHandler 子接口:
- ChannelInboundHandler——处理入站数据以及各种状态变化;
- ChannelOutboundHandler——处理出站数据并且允许拦截所有的操作。

ChannelInboundHandler 接口
入站操作和数据将由 ChannelInboundHandler 处理。
这里写图片描述

ChannelOutboundHandler 接口
出站操作和数据将由 ChannelOutboundHandler 处理。它的方法将被 Channel、ChannelPipeline 以及 ChannelHandlerContext 调用。
这里写图片描述

ChannelPromise与ChannelFuture ,ChannelOutboundHandler中的大部分方法都需要一个
ChannelPromise参数, 以便在操作完成时得到通知。 ChannelPromise是ChannelFuture的一个
子类,其定义了一些可写的方法,如setSuccess()和setFailure(), 从而使ChannelFuture不可变


ChannelHandlerAdapter功能说明
对于大多数的ChannelHandler会选择性地拦截或者处理某些事件,其他的事件会忽略,由下一个ChannelHandler进行拦截处理。这就导致了一个问题,用户ChannelHandler必须要实现ChanelHandler所有的接口方法,包括它不关心的事件处理接口,这会导致用户的业务代码臃肿,代码可维护性也会变得很差。
为了解决这个问题,Netty提供了ChannelHandlerAdapter基类,它的所有接口实现都是事件透传(且添加了@Skip注解),如果用户对哪个事件感兴趣,只需要覆盖对应的方法即可:

public class ChannelHandlerAdapter implements ChannelHandler {

    // .... 
    @Skip
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        // NOOP
    }

    @Skip
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        // NOOP
    }
}

ChannelHandlerAdapter类图
这里写图片描述

  • ByteToMessageDecoder :将读取到的字节数组或者字节缓冲区解码为业务可以使用的POJO对象。用户只需要实现decode(ChannelHandlerContext ctx, ByteBuf in, List out)方法即可,注意:ByteToMessageDecoder并没有考虑TCP拆包/粘包场景

  • MessageToMessageDecoder:它实际上是Netty的一个二次解码器,它的职责将一个对象二次解码为其他对象。为什么称之为二次解码器?因为从SocketChannel读取的TCP数据报是ByteBuffer,它实际上是字节数组,首先要将它解码为一个Java对象,然后再通过MessageToMessageDecoder将它解码为另一个POJO对象。例如Http+xml协议,通常第一次解码为HttpRequest对象,然后将HttpRequest消息体字符串进行二次解码,将XML格式字符串解码为POJO对象。

  • MessageToByteEncoder:它负责将POJO对象转换为ByteBuf,用户集成MessageToByteEncoder需要实现它的encode(ChannelHandlerContext ctx, I msg, ByteBuf out)方法。
  • MessageToMessageEncoder:实现encode(ChannelHandlerContext ctx, I msg, List out)方法即可。它与MessageToByteEncoder的不同之处是:它输出的是List
  • ByteToMessageCodec : ByteToMessageDecoder + MessageToByteEncoder
  • MessageToMessageCodec: MessageToMessageDecoder + MessageToMessageEncoder

ChannelPipeline

ChannelPipeline 功能说明
每一个新创建的 Channel 都将会被分配一个新的 ChannelPipeline。ChannelPipeline是ChannelHandler的容器,它负责Channel的管理和事件拦截调度,ChannelPipeline可以根据需要,**动态的增加或者删除**ChannelHandler。
这里写图片描述
如果一个入站事件被触发,它将从ChannelPipeline头部一直传播到尾端,如果出站事件触发,则按照相反的方向传播。

ChannelPipeline的事件分为inbound事件和outbound事件。
ChannelPipeline的入站事件
入站事件通常是由I/O线程触发,如TCP建立连接,链路关闭事件,读事件,异常通知事件
这里写图片描述

ChannelPipeline的出站事件
出站事件通常是用户主动发起网路I/O操作,如用户发起连接操作,绑定操作,消息发送操作等。
这里写图片描述

ChannelPipeline类结构
这里写图片描述


ChannelHandlerContext

ChannelHandlerContext代表了ChannelHandler和ChannelPipeline之间的关联,
每当有ChannelHandler添加到ChannelPipeline中时,都会创建ChannelHandlerContext。
ChannelHandlerContext的主要功能是管理它所关联的ChannelHandler和在同一个ChannelPipeline中的其他ChannelHandler之间的交互。
- ChannelHandlerContext和ChannelHandler之间的关联(绑定)是永远不会改变的,所以缓存对它的引用是安全的;

这里写图片描述

ChannelHandlerContext接口-主要方法
这里写图片描述

ChannelHandlerContext有很多的方法,其中一些方法也存在于Channel和ChannelPipeline本身上,但是有一点重要的不同。如果调用Channel或者ChannelPipeline上的这些方法,它们将沿着整个ChannelPipeline进行传播。而调用位于ChannelHandlerContext上的相同方法,则将从当前所关联的ChannelHandler开始,并且只会传播给位于该ChannelPipeline中的下一个能够处理该事件的ChannelHandler。

通过Channel或者ChannelPipeline写入

    void channelWrite(){
        ChannelHandlerContext ctx = null;
        Channel channel = ctx.channel();
        //通过 Channel 写入缓冲区, 将会导致写入事件从尾端到头部地流经 ChannelPipeline。
        channel.write(Unpooled.copiedBuffer("Netty in Action", CharsetUtil.UTF_8));
    }

    void pipelineWrite(){
        ChannelHandlerContext ctx = null;
        ChannelPipeline pipeline = ctx.pipeline();
        //通过 ChannelPipeline 写入缓冲区, 同Channel
        pipeline.write(Unpooled.copiedBuffer("Netty in Action",CharsetUtil.UTF_8));
    }

调用Channel或ChannelPipeline上的write()方法将一直传播事件通过整个ChannelPipeline,但是在ChannelHandler的级别上,事件从一个ChannelHandler到下一个ChannelHandler的移动是由ChannelHandlerContext上的调用完成的.

这里写图片描述
为什么需要从ChannelPipeline的某个特定的点开始事件传播呢?
- 为了减少将事件传经对它不感兴趣的 ChannelHandler 所带来的开销。
- 为了避免将事件传经那些可能会对它感兴趣的 ChannelHandler。

通过ChannelHandlerContext写入

    void contextWrite(){
        ChannelHandlerContext ctx = null;
        //通过ChannelHandlerContext写入缓冲区,write()方法将把缓冲区数据发送到下一个 ChannelHandler
        ctx.write(Unpooled.copiedBuffer("Netty in Action",CharsetUtil.UTF_8));
    }

这里写图片描述


异常

异常处理是任何真实应用程序的重要组成部分,它也可以通过多种方式来实现。因此, Netty提供了几种方式用于处理入站或者出站处理过程中所抛出的异常。

入站异常

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
  • hannelHandler.exceptionCaught()的默认实现是简单地将当前异常转发给ChannelPipeline 中的下一个 ChannelHandler;
  • 如果异常到达了 ChannelPipeline 的尾端,它将会被记录为未被处理;
  • 要想定义自定义的处理逻辑,你需要重写 exceptionCaught()方法。然后你需要决定是否需要将该异常传播出去。

出站异常
用于处理出站操作中的正常完成以及异常的选项, 都基于以下的通知机制。

  • 每个出站操作都将返回一个 ChannelFuture。 注册到 ChannelFuture 的 ChannelFutureListener 将在操作完成时被通知该操作是成功了还是出错了。
  • 几乎所有的 ChannelOutboundHandler 上的方法都会传入一个 ChannelPromise的实例。作为 ChannelFuture 的子类, ChannelPromise 也可以被分配用于异步通知的监听器。
public ChannelFuture write(Object msg);
public void write(ChannelHandlerContext ctx, Object msg,ChannelPromise promise);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值