Netty笔记(二)之ChannelHandler与ChannelPipeline

netty版本

  1. netty版本:io.netty:netty-all:4.1.33.Final

Channel

  1. Channel代表一个到实体(如一个硬件设备、一个文件、一个Socket或者一个能够执行一个或者多个不同的IO操作的程序组件)的开放连接,如读和写操作。Channel是对Socket的抽象,Channel接口提供的API,大大的降低了Socket类的复杂性

  2. 可以把Channel看作是传入或者传出数据的载体,Channel可以被打开或者被关闭,连接或者断开连接

  3. 使用java原生API进行网络编程,当你想从阻塞传输切换到非阻塞传输,这个切换不是那么容易的,反之亦然。Netty在它的传输协议实现上提供了统一的API,使得这种解决方案更简单。传输API的核心是channel接口,用于所有的outbound操作.

  4. 从上图中可以看到,每个Channel都将会被分配一个ChannelPipelineChannelConfigChannelConfig包含了该Channel的所有配置设置,并且支持热更新。由于特定的传输可能具有独特的设置,所以它可能会实现一个ChannelConfig的子类型

  5. Channel重要方法

    方法名称描述
    eventLoop()返回分配给ChannelEventLoop
    pipeline()返回分配给ChannelChannelPipeline
    isActive()如果Channel是活动的,则返回 true。活动的意义可能依赖于底层的传输。例如,一个Socket传输一旦连接到了远程节点便是活动的,而一个Datagram传输一旦被打开便是活动的
    localAddress()返回绑定到本地的SocketAddress
    remoteAddress()返回绑定到远程的SocketAddress
    write()将数据写到远程节点。这个数据将被传递给ChannelPipeline, 并且排队直到它被flush
    flush将之前已写的数据冲刷到底层传输,如一个Socket
    writeAndFlush一个简便的方法,等同于调用 write() 并接着调用flush()
  6. Netty中Channel的特性

    • Channel是独一无二的
    • Channel是线程安全的

Channel生命周期

  1. Channel的生命周期状态

    状态描述
    ChannelUnregisteredChannel已创建,还未注册到一个EventLoop
    ChannelRegisteredChannel已经注册到一个EventLoop
    ChannelActiveChannel是活跃状态(连接到某个远端),可以收发数据
    ChannelInactiveChannel未连接到远端

  2. Channel状态发生变化时,将会生成对应的事件,这些事件会被转发给ChannelPipeline中的ChannelHandler

ChannelHandler

  1. ChannelHandler不仅仅充当所有处理入站和出站数据的应用程序逻辑的容器,而且还可以处理其他的动作,比如将数据从一种格式转换为另外一种格式,或者处理转换过程中所抛出的异常等。
  2. ChannelHandler典型的用法
    • 将数据从一种格式转换为另一种格式
    • 提供异常的通知
    • 提供Channel变为活动的或者非活动的通知
    • 提供当Channel注册到EventLoop或者从EventLoop注销时的通知;
    • 提供有关用户自定义事件的通知

ChannelHandler生命周期

  1. ChannelHandler生命周期

    类型描述
    handlerAdded当把ChannelHandler添加到ChannelPipeline中时被调用
    handlerRemoved当从ChannelPipeline中移除ChannelHandler时被调用
    exceptionCaught当处理过程中在ChannelPipeline中有错误产生时被调用
  2. ChannelHandler接口方法

        public interface ChannelHandler {
            void handlerAdded(ChannelHandlerContext ctx) throws Exception;
            void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
            void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
            @Inherited
            @Documented
            @Target(ElementType.TYPE)
            @Retention(RetentionPolicy.RUNTIME)
            @interface Sharable {
                // no value
            }
        }
    
    
    
  3. Netty定义了两个重要的ChannelHandler子接口

    • ChannelInboundHandler:处理入站数据以及各种状态变化
    • ChannelOutboundHandler:处理出站数据并且允许拦截所有的操作

ChannelInboundHandler

  1. 方法

    类型描述
    channelRegisteredChannel已经注册到它的EventLoop并且能够处理I/O时被调用
    channelUnregisteredChannel从它的EventLoop注销并且无法处理任何I/O才被调用
    channelActiveChannel处于活动状态时被调用,Channel已经连接/绑定并且已经就绪
    channelInactiveChannel离开活动状态并且不再连接它的远程节点时被调用
    channelReadCompleteChannel上的一个读操作完成时被调用,可能在 channelReadComplete()被调用之前看到多次调用channelRead()
    channelRead当从Channel读取数据时被调用,当某个ChannelInboundHandler的实现重写了此方法时,需要手动显示的释放与池化ByteBuf实例相关的内存,Netty提供了ReferenceCountUtil.release(msg)来手动释放
    ChannelWritabilityChangedChannel的可写状态发生改变时被调用。 用户可以确保写操作不会完成得太快(以避免发生OutOfMemoryError)或者可以在Channel变为再次可写时恢复写入。可以通过调用ChannelisWritable()方法来检测Channel的可写性。与可写性相关的阈值可以通过Channel.config().setWriteHighWaterMark()Channel.config().setWriteLowWaterMark()方法来设置
    userEventTriggeredChannelnboundHandler.fireUserEventTriggered()方法被调用时被调用,因为一个POJO被传经了ChannelPipeline
  2. 常规的运行顺序

        //连接成功
        handlerAdded()
        channelActive()
        //客户端发送数据过来
        channelRead()
        channelReadComplete()
        //连接失效
        channelInactive()
        channelUnregistered
        handlerRemoved()
    

ChannelOutboundHandler

  1. 出站操作和数据将由ChannelOutboundHandler处理。它的方法将被ChannelChannel­Pipeline以及ChannelHandlerContext调用。

  2. ChannelOutboundHandler的一个强大的功能是可以按需推迟操作或者事件,这使得可以通过一些复杂的方法来处理请求。例如:如果到远程节点的写入被暂停了,那么你可以推迟冲刷操作井在稍后继续。

  3. ChannelOutboundHandler接口的方法

    方法描述
    bind(ChannelHandlerContext, SocketAddress , ChannelPromise)当请求将 Channel 绑定到本地地址时被调用
    connect(ChannelHandlerContext , SocketAddress , SocketAddress , ChannelPromise)当请求将 Channel 连接到远程节点时被调用
    disconnect(ChannelHandlerContext, ChannelPromise)当请求将 Channel 从远程节点断开时被调用
    close(ChannelHandlerContext ctx, ChannelPromise promise)当请求关闭 Channel 时被调用
    deregister(ChannelHandlerContext ctx, ChannelPromise promise)当请求将Channel从它的EventLoop注销时被调用
    read(ChannelHandlerContext)当请求从 Channel 读取更多的数据时被调用
    flush(ChannelHandlerContext)当请求通过 Channel 将入队数据冲刷到远程节点时被调用
    write(ChannelHandlerContext,Object, ChannelPromise)当请求通过 Channel将数据写到远程节点时被调用
  4. ChannelPromiseChannelFuture ChannelOutboundHandler 中的大部分方法都需要一个ChannelPromise参数,以便在操作完成时得到通知。ChannelPromiseChannelFuture的一个子类,其定义了一些可写的方法,如setSuccess()setFailure(),从而使ChannelFuture不可变(当一个Promise被完成之后,其对应的Future的值便不能再进行任何修改了)

ChannelHandler适配器

  1. ChannelinboundHandlerAdapterChannelOutboundHandlerAdapter分别提供了ChannelinboundHandlerChannelOutboundHandler的基本实现

  2. ChannelHandlerAdapter还提供了实用方法isSharable()。 如果其对应的实现被标注为@Sharable,那么这个方法将返回true,表示它可以被添加到多个ChannelPipeline。当收集跨域多个Channel的统计信息时,我们可能需要共享通过一个ChannelHandler

资源管理

  1. 当通过调用ChannelinboundHandler.channelRead()或者ChannelOutboundHandler.write()方法来处理数据时,都需要确保没有任何的资源泄露。Netty使用引用计数来处理池化的ByteBuf,所以在完全使用完ByteBuf后,调整其引用计数很重要。

  2. 为了帮助诊断潜在的资源泄露问题,Netty提供了ResourceLeakDetector,它将对你应用程序的缓冲区分配做大约 1%的采样来检测内存泄露,相关的开销是非常小的。如果检测到内存泄露,会调用reportUntracedLeak方法打印错误日志

       protected void reportUntracedLeak(String resourceType) {
            logger.error("LEAK: {}.release() was not called before it's garbage-collected. " +
                    "Enable advanced leak reporting to find out where the leak occurred. " +
                    "To enable advanced leak reporting, " +
                    "specify the JVM option '-D{}={}' or call {}.setLevel() " +
                    "See http://netty.io/wiki/reference-counted-objects.html for more information.",
                    resourceType, PROP_LEVEL, Level.ADVANCED.name().toLowerCase(), simpleClassName(this));
        } 
    
  3. Netty定义了集中检测级别ResourceLeakDetector.Level

    级别描述
    DISABLED禁用泄漏检测。只有在详尽的测试之后才应设置为这个值
    SIMPLE使用 1%的默认采样率检测并报告任何发现的泄露。这是默认级别,适合绝大部分的情况
    ADVANCED使用默认的采样率,报告所发现的任何的泄露以及对应的消息被访问的位置
    PARANOID类似于 ADVANCED,但是其将会对每次(对消息的)访问都进行来样。这对性能将会有很大的影响,应该只在调试阶段使用
  4. 内存泄露检测级别可以通过java系统属性来定义java -Dio.netty.leakDetection.level=ADVANCED 或者java -Dio.netty.leakDetectionLevel=ADVANCED,对应ResourceLeakDetector类的属性。如果设置新的就以新的为准

        //老的 
        private static final String PROP_LEVEL_OLD = "io.netty.leakDetectionLevel";
        //新的
        private static final String PROP_LEVEL = "io.netty.leakDetection.level";
        
        // First read old property name
        String levelStr = SystemPropertyUtil.get(PROP_LEVEL_OLD, defaultLevel.name());
        // If new property name is present, use it
        levelStr = SystemPropertyUtil.get(PROP_LEVEL, levelStr);    
    
  5. 如果不需要入站数据在channelRead()中直接消费而不需要转发到下一个ChannelInboundHandler,可以继承SimpleChannelinboundHandler并覆写channelRead0()方法自动释放消息,因为SimpleChannelInboundHandler自动释放资源,任何对消息的引用都会变成无效,所以你不能保存这些引用待后来使用.

  6. 对于出站数据,如果处理了writer()操作并丢弃一个消息,那么应该负责释放它,并且通知ChannelPromise数据已经被处理了(否则可能会出现ChannelFutureListener收不到某个消息已经被处理了的通知的情况)。

        public void write(ChannelHandlerContext ctx,Object msg,ChannelPromise promise){
            ReferenceCountUtil. release (msg);
            promise.setSuccess();
        }
    

ChannelPipeline

  1. Netty允许自定义ChannelHandler的实现来处理数据。ChannelPipelineChannelHandler实例的列表,用于处理或截获Channel的接收和发送数据。ChannelPipeline提供了一种高级的截取过滤器模式,让用户可以在ChannelPipeline中完全控制一个事件及如何处理ChannelHandlerChannelPipeline的交互
  2. Channel被创建时,它会被自动地分配到它专属的ChannelPipeline。一旦连接,ChannelChannelPipeline的耦合性是永久的,Channel不能附加其他的ChannelPipeline或从ChannelPipeline分离。 每个Channel都有一个属于自己的ChannelPipeline,调用Channel#pipeline()方法可以获得ChannelChannelPipeline,调用Pipeline#channel()方法可以获得ChannelPipelineChannel
  3. 很明显,ChannelPipeline里面就是一个ChannelHandler的列表。ChannelHandler的执行顺序是由添加顺序决定的。如果一个入站IO事件被触发,这个事件会从第一个开始依次通过ChannelPipeline中的ChannelHandler。若是一个出站I/O事件,则会从最后一个开始依次通过ChannelPipeline中的ChannelHandlerChannelHandler可以处理事件并检查类型,如果某个ChannelHandler不能处理则会跳过,并将事件传递到下一个ChannelHandlerChannelPipeline可以动态添加、删除、替换其中的ChannelHandler,这样的机制可以提高灵活性。
  4. 总结
    • ChannelPipeline保存了与Channel相关联的ChannelHandler;
    • ChannelPipeline可以根据需要,通过添加或者删除ChannelHandler来动态地修改。例如当业务高峰期需要对系统做拥塞保护时,根据时间判断,动态地将系统拥塞保护ChannelHandler添加到当前的ChannelPipeline中。当高峰期过去之后,可以动态删除
    • ChannelPipeline有着丰富的API用以被调用,以响应入站和出站事件
    • 入站调用顺序与ChannelInboundHandler添加顺序一样,出站调用顺序与ChannelOutboundHandler添加顺序相反
    • ChannelPipeline是线程安全的

ChannelHandlerContext

  1. ChannelHandlerContext使得ChannelHandler能够和它的ChannelPipeline 以及其他的ChannelHandler交互。ChannelHandler可以通知其所属的ChannelPipeline中的下一个ChannelHandler,甚至可以动态修改它所属的 ChannelPipeline中的ChannelHandler的编排(顺序)

  2. 每当有ChannelHandler添加到ChannelPipeline中时,都会创建ChannelHandlerContextChannelHandlerContext有很多与ChannelChannelPipeline类似的方法,如果调用ChannelChannelPipeline这些方法它们将沿着整个ChannelPipeline进行传播,而调用位于ChannelHandlerContext上相同的方法,则将从当前所关联的ChannelHandler开始,并且只会传播给位于该ChannelPipeline中的下一个能够处理该事件的ChannelHandler

  3. ChannelHandlerContextChannelPipelineChannelHandler关系图


  4. 方法

    方法描述
    alloc返回和这个实例相关联的 Channel 所配置的 ByteBufAllocator
    bind绑定到给定的 SocketAddress,并返回 ChannelFuture
    channel返回绑定到这个实例的 Channel
    close关闭 Channel,并返回 ChannelFuture
    connect连接给定的 SocketAddress,并返回 ChannelFuture
    deregister从之前分配的 EventExecutor 注销,并返回 ChannelFuture
    disconnect从远程节点断开,并返回 ChannelFuture
    executor返回调度事件的 EventExecutor
    fireChannelActive触发对下一个 ChannelInboundHandler 上的 channelActive()方法(己连接)的调用
    fireChannelinactive触发对下一个 ChannelInboundHandler 上的 channelInactive()方法(己关闭〉的调用
    fireChannelRead触发对下一个 ChannelInboundHandler 上的 channelRead() 方法(己接收的消息)的调用
    fireChannelReadComplete触发对下一个 ChannelInboundHandler 上的 channelReadComplete ()方法的调用
    fireChannelRegistered触发对下 一个 ChannelInboundHandler 上的fireChannelRegistered ()方法的调用
    fireChannelUnregistered触发对下 一个 ChannelInboundHandler 上的 fireChannelUnregistered ()方法的调用
    fireChannelWritabilityChanged触发对下 一个 ChannelInboundHandler 上的 fireChannelWritabilityChanged ()方法的调用
    fireExceptionCaught触发对下 一个 ChannelInboundHandler 上的 fireExceptionCaught(Throwable)方法的调用
    fireUserEventTriggered触发对下 一个 ChannelInboundHandler 上的 fireUserEventTriggered(Object evt)方法的调用
    handler返回绑定到这个实例的 ChannelHandler
    isRemoved如果所关联的 ChannelHandler 已经被从 ChannelPipeline 中移除则返回 true
    name返回这个实例的唯一名称
    pipeline返回这个实例所关联的 ChannelPipeline
    read将数据从 Channel 读取到第一个入站缓冲区;如果读取成功则触发一个 channelRead事件,并(在最后一个消息被读取完成后)通知 ChannelInboundHandler 的 channelReadComplete(ChannelHandlerContext)方法
    write通过这个实例写入消息并经过 ChannelPipeline
    writeAndFlush通过这个实例写入并冲刷消息并经过 ChannelPipeline
  5. 虽然ChannelHandlerContext可以被用于获取底层的Channel,但是它主要还是被用于写出站数据。

修改ChannelPipeline

  1. ChannelHandler可以通过添加、删除或者替换其他的ChannelHandler来实时地修改ChannelPipeline的布局(它也可以将它自己从ChannelPipeline中移除)

    方法描述
    addFirst添加ChannelHandler在ChannelPipeline的第一个位置
    addBefore在ChannelPipeline中指定的ChannelHandler名称之前添加ChannelHandler
    addAfter在ChannelPipeline中指定的ChannelHandler名称之后添加ChannelHandler
    addLast在ChannelPipeline的末尾添加ChannelHandler
    remove删除ChannelPipeline中指定的ChannelHandler
    replace替换ChannelPipeline中指定的ChannelHandler
  2. 通常ChannelPipeline中的每一个ChannelHandler都是通过它的EventLoop(I/O线程)来处理传递给它的事件的。所以不要阻塞这个线程,因为这会对整体I/O处理产生负面的影响。有的时候可能需要与那些使用阻塞API的遗留代码进行交互,比如JDBC。对于这种情况,ChannelPipeline有一些接受一个EventExecutorGroup的add()方法。如果一个事件被传递给一个自定义的EventExecutorGroup,它将被包含在这个EventExecutorGroup中的某个EventExecutor所处理,从而被从该Channel本身的EventLoop中移除。对于这种,Netty提供了默认实现DefaultEventExecutorGroup

  3. ChannelPipeline用于访问Channelhandler的方法

    方法描述
    get通过类型或者名称返回ChannelHandler
    context返回和ChannelHandler绑定的ChannelHandlerContext
    names返回ChannelPipeline中所有的Channelhandler的名称

ChannelPipeline触发事件

  1. 入站操作

    方法描述
    fireChannelRegistered调用ChannelPipeline中下一个 ChannelInboundHandler的channelRegistered(ChannelHandlerContext)方法
    fireChannelUnregistered调用ChannelPipeline 中下一个 ChannelInboundHandler的channelUnregistered(ChannelHandlerContext)方法
    fireChannelActive调用ChannelPipeline中下一个ChannelInboundHandler 的 channelActive (ChannelHandlerContext)方法
    fireChannelInactive调用 ChannelPipeline 中下一个 ChannelInboundHandler 的 channelInactive(ChannelHandlerContext )方法
    fireExceptionCaught调用 ChannelPipeline中下一个 ChannelInboundHandler的exceptionCaught(ChannelHandlerContext, Throwable)方法
    fireUserEventTriggered调用ChannelPipeline中下一个 ChannelInboundHandler的userEventTriggered(ChannelHandlerContext, Object)方法
    fireChannelRead调用ChannelPipeline中下一个ChannelInboundHandler 的 channelRead (ChannelHandlerContext, Object msg)方法
    fireChannelReadComplete调用 ChannelPipeline 中下一个 ChannelInboundHandler 的channelReadComplete(ChannelHandlerContext)方法
    fireChannelWritabilityChanged调用 ChannelPipeline 中下一个 ChannelInboundHandler的channelWritabilityChanged(ChannelHandlerCon.text)方法
  2. 出站操作

    方法描述
    bind将Channel绑定到一个本地地址,这将调用 ChannelPipeline 中的下一个 ChannelOutboundHandler 的 bind(ChannelHandlerContext, SocketAddress , ChannelPromise)方法
    connect将Channel连接到一个远程地址,这将调用 ChannelPipeline 中的下一个 ChannelOutboundHandler的connect(ChannelHandlerContext,SocketAddress, ChannelPromise)方法
    disconnect将Channel断开连接。这将调用ChannelPipeline中的下一个ChannelOutboundHandler的disconnect (ChannelHandlerContext,ChannelPromise)方法
    close将 Channel 关闭 。这将调用 ChannelPipeline 中的下一个 ChannelOutboundHandler 的 close(ChannelHandlerContext, ChannelPromise)方法
    deregister将 Channel 从它先前所分配的 EventExecutor (即 EventLoop)中注销。这将调用 ChannelPipeline中的下一个ChannelOutboundHandler的deregister(ChannelHandlerContext,ChannelPromise)方法
    flush冲刷 Channel所有挂起的写入。这将调用 ChannelPipeline 中的下一个 ChannelOutboundHandler 的 flush(ChannelHandlerContext)方法
    write将消息写入Channel。这将调用 ChannelPipeline中的下一个ChannelOutboundHandler的write(ChannelHandlerContext , Object msg , ChannelPromise)方法。注意:这并不会将消息写入底层的 Socket, 而只会将它放入队列中。 要将它写入 Socket,需要调用 flush ()或者 writeAndFlush()方法
    writeAndFlush这是一个先调用 write ()方法再接着调用 flush ()方法的便利方法
    read请求从 Channel 中读取更多的数据。这将调用 ChannelPipeline 中的下一个 ChannelOutboundHandler的read(ChannelHandlerContext)方法
  3. 从性能角度考虑,为了防止频繁唤醒Selector进行消息发送,Netty的write()方法并不直接将消息写入SocketChannel中,调用write()方法只是把待发送的消息放到发送缓冲数组中,再通过flush方法,将发送缓冲区中的消息全部写到SocketChannel中。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值