netty基础05_管道和消息处理器

ChannelHandler是消息处理器;
ChannelHandler接口中定义了很多方法,每一个方法对应一个事件;
当Channel和ChannelHandler生命周期中状态改变时会触发各种事件,也就导致了事件对应的方法会被调用;
 
1.Channel的生命周期
netty用Channel表示一个连接;
Channel有4种状态:
 
一个Channel的生命周期中状态的改变:创建    -》注册    -》激活    -》连接断开;
 
每当Channel的状态发生改变时,将会生成对应的事件。
这些事件将会被转发给 ChannelPipeline 中的 ChannelHandler;
可以在ChannelHandler中重写方法来对这些事件进行处理;
 
2.ChannelHadler生命周期
ChannelHandler接口中定义了一些方法,在其生命周期中触发事件时,事件对应的方法会被调用;
每一个方法都接受一个 ChannelHandlerContext 参数;
 
ChannelHandler接口有两个子接口:ChannelInboundHandler、ChannelOutboundHandler ,分别用来处理进站消息和出站消息;  
两个子接口都为各自生命周期中的事件定义了对应的方法; 
 
1) ChannelInboundHandler的生命周期
ChannelInboundHandler是ChannelHadler的子接口,用来处理进站消息;
ChannelInboundHandler定义了一些方法;
这些方法将会在数据被接收时或者与其对应的 Channel 状态发生改变时被调用;
netty提供了适配器ChannelInboundHandlerAdapter来实现这些方法;
需要处理消息进站事件时,可以考虑通过继承适配器来实现;
 
释放资源:
当某个 ChannelInboundHandler 的实现重写 channelRead()方法时,它将负责显式地释放与池化的 ByteBuf 实例相关的内存;
 Netty 为此提供了一个实用方法 ReferenceCountUtil.release();
public class DiscardHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ReferenceCountUtil.release(msg);
    }
}
 
netty提供的SimpleChannelInboundHandler扩展了ChannelInboundHandlerAdapter,可以使用来做一些常用的简单消息处理;
SimpleChannelInboundHandler 会自动释放资源,不需要显示释放资源;
不应该存储指向任何消息的引用供将来使用,因为这些引用都将会失效;
public class SimpleDiscardHandler
extends SimpleChannelInboundHandler<Object> {
    @Override
    public void channelRead0(ChannelHandlerContext ctx,Object msg) {
        // 不需要在这里释放资源
    }
}
 
3) ChannelOutboundHandler的生命周期
ChannelOutboundHandler也是ChannelHandler的一个子接口,用来处理出站消息;
几乎所有的方法都将 ChannelPromise 作为参数;
ChannelPromise继承自ChannelFuture,用来返回消息处理的结果;
适配器 ChannelOutboundHandlerAdapter提供了这些方法的基本实现,可以拓展适配器来实现自己的业务;
 
3.资源管理
调用 ChannelInboundHandler.channelRead()或者 ChannelOutboundHandler.write()方法来处理数据时,需要确保没有任何的资源泄漏;
Netty 使用引用计数来处理池化的 ByteBuf;
因此使用完某个ByteBuf 后,需要调整其引用计数来释放资源;
 
1)资源泄露
Netty提供了一个类ResourceLeakDetector来诊断潜在的资源泄漏问题;
有4种泄露检测级别:
ResourceLeakDetector默认采用SIMPLE级别的检测;
如果检测到了内存泄露,将会产生类似于下面的日志消息:
LEAK: ByteBuf.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
'-Dio.netty.leakDetectionLevel=ADVANCED' or call
ResourceLeakDetector.setLevel().
此时可以根据提示提高检测级别来找到找到问题位置;
 
设置检测级别:
    泄露检测级别可以通过将下面的 Java 系统属性设置为表中的一个值来定义:
    java -Dio.netty.leakDetectionLevel=ADVANCED
    也可以调用ResourceLeakDetector的setLevel()方法;
 
将检测级别设置为ADVANCED后,重启程序,可以找到资源泄露的位置;
例如:
Running io.netty.handler.codec.xml.XmlFrameDecoderTest
15:03:36.886 [main] ERROR io.netty.util.ResourceLeakDetector - LEAK:
ByteBuf.release() was not called before it's garbage-collected.
Recent access records: 1
#1: io.netty.buffer.AdvancedLeakAwareByteBuf.toString(
AdvancedLeakAwareByteBuf.java:697)
io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithXml(
XmlFrameDecoderTest.java:157)
io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithTwoMessages(
XmlFrameDecoderTest.java:133)
 
2)资源释放
1】入站消息
当重写ChannelInboundHandler的channelRead方法时,可以通过ReferenceCountUtil.release(msg)来释放消息;
 
如果是消息处理器是继承SimpleChannelInboundHandler,重写channelRead0方法,则不需要显示释放;
    这个实现会在消息被 channelRead0()方法消费之后自动释放消息。
 
2】出站消息
如果重写了ChannelOutboundHandler的write方法,并且需要丢弃消息时,需要释放资源;
public class DiscardOutboundHandler
extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx,
    Object msg, ChannelPromise promise) {
        ReferenceCountUtil.release(msg);    //释放消息
        promise.setSuccess();                //通知ChannelPromise数据已被处理
    }
}
调用ReferenceCountUtil.release(msg)来释放资源;
不仅要释放资源,还要通知 ChannelPromise,否则可能会出现 ChannelFutureListener 收不到某个消息已经被处理了的通知的情况;
 
总之:
    如果一个消息被消费或者丢弃了, 并且没有传递给 ChannelPipeline 中的下一个ChannelOutboundHandler,
    那么用户就有责任调用 ReferenceCountUtil.release()来释放资源
    如果消息到达了实际的传输层, 那么当它被写入时或者 Channel 关闭时,都将被自动释放。
 
4.管道 (ChannelPipeline)
管道是ChannelHandler的容器;
每一次创建了新的Channel ,都会新建一个新的 ChannelPipeline并绑定到Channel上。
channel和管道的关联是永久性的,Channel既不能附上另一个ChannelPipeline 也不能分离当前这个。
 
1)关于管道
ChannelPipeline 主要由一系列的ChannelHandler所组成的;
Netty 总是将 ChannelPipeline 的入站口作为头部,而将出站口作为尾端;
ChannelPipeline提供了通过ChannelPipeline本身传播事件的方法:
    如果一个入站事件被触发,它将被从 ChannelPipeline 的头部开始一直被传播到 Channel Pipeline 的尾端;
    一个出站 I/O 事件将从 ChannelPipeline 的最右边开始,然后向左传播;
 
2)管道中处理器执行顺序
在 ChannelPipeline 传播事件时,它会测试 ChannelPipeline 中的下一个 ChannelHandler 的类型是否和事件的运动方向相匹配;
如果不匹配, ChannelPipeline 将跳过该ChannelHandler 并前进到下一个,直到它找到和该事件所期望的方向相匹配的为止;
 
也就是说:
    对于channelInboundHandler,总是会从传递事件的开始,向链表末尾方向遍历执行可用的inboundHandler。
    对于channelOutboundHandler,总是会从write事件执行的开始,向链表头部方向遍历执行可用的outboundHandler。
 
例如:
ch.pipeline().addLast(new OutboundHandler1());  
ch.pipeline().addLast(new OutboundHandler2());  
ch.pipeline().addLast(new InboundHandler1());  
ch.pipeline().addLast(new InboundHandler2());
管道中处理器的顺序为head->out1->out2->in1->in2->tail
Inbound的执行顺序为read->in1->in2
在Inbound执行write后,outbound执行顺序为out1<-out2<-write
 
总结:
    InboundHandler是按顺序传递,OutboundHandler是逆序传递。
 
3) ChannelHandler 的执行和阻塞
通常 ChannelPipeline 中的每一个 ChannelHandler 都是通过它的 EventLoop(I/O 线程)来处理传递给它的事件的;
为了防止对整体的 I/O 处理产生负面的影响,最好不要阻塞这个线程;
但有时可能需要与那些使用阻塞 API 的遗留代码进行交互,从而导致EventLoop中的线程被阻塞;
对于这种情况,netty提供了EventExecutorGroup;
EventExecutorGroup 就是专门来处理耗时业务的线程池;
如果一个事件被传递给一个自定义的 EventExecutorGroup,它将被包含在这个 EventExecutorGroup 中的某个 EventExecutor 所处理,从而被从该Channel 本身的 EventLoop 中移除。
Netty 提供了一个叫EventExecutorGroup的默认实现DefaultEventExecutorGroup;
 
ChannelPipeline 有一些接受一个 EventExecutorGroup 的 add()方法;
在管道中添加消息处理器时,可以带上EventExecutorGroup的子类实例;
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {    //使用处理器初始化器,可以在管道中添加多个处理器
    static final EventExecutorGroup group = new DefaultEventExecutorGroup(16);    //EventExecutorGroup的实例
    @Override
    protected void initChannel(SocketChannel ch)throws Exception {
        ChannelPipeline p = ch.pipeline();
        //在管道中添加消息处理器,如果处理耗时任务或可能阻塞线程时可带上EventExecutorGroup作为参数,将消息交给EventExecutorGroup处理,防止影响整体io速度
        pipeline.addLast(group, "handler", new MyBusinessLogicHandler());    
    }
}
 
4)管道api
1】编辑管道
例如:
ChannelPipeline pipeline = ...; // 得到管道的引用
FirstHandler firstHandler = new FirstHandler(); 
pipeline.addLast("handler1", firstHandler);     //往管道尾部中添加消息处理器1:h1
pipeline.addFirst("handler2", new SecondHandler()); //处理器2添加到管道头部:h2->h1
pipeline.addLast("handler3", new ThirdHandler());    //处理器3添加到尾部:h2->h1->h3 
pipeline.remove("handler3");     //(通过名字移除)从管道中移除h3:h2->h1
pipeline.remove(firstHandler);    //(通过引用移除)从管道中移除h1:h2 
pipeline.replace("handler2", "handler4", new ForthHandler()); //用h4替换h2:h4
 
2】查询管道
 
3】触发事件
入站事件:
 
出站事件:
 
3.上下文 ChannelHandlerContext
ChannelHandlerContext表示ChannelHandler和管道之间的关联;
当一个ChannelHandler加入到管道中时会创建一个ChannelHandlerContext实例;
ChannelHandler通过ChannelHandlerContext与同一个管道中的其它ChannelHandler交互;
 
@Sharable 注解
    一个ChannelHandler实例可能会加入到多个管道中,因此一个ChannelHandler可能对应多个ChannelHandlerContext;
    ChannelHandler添加到多个管道时需要在ChannelHandler的实现类添加@Sharable 注解,否则将引发一个异常。
    需要在保证线程安全的情况下使用@Sharable 注解;(例如需要在事件处理方法中修改状态时需要同步)
 
api:
ChannelHandlerContext中也有write方法,用来写出一个出站消息,和Channel;
Channel和ChannelPipeline的write方法写出的消息会从管道尾部开始传递,依次被管道上每一个出站消息处理器处理;
而ChannelHandlerContext的write写出的消息会从当前ChannelHandler的前一个出站消息处理器开始传递;
 
4.异常处理
1)进站异常处理
如果在处理进站事件的过程中有异常被抛出,这个异常消息将从异常所产生的的 ChannelInboundHandler那里开始传递;
 
异常处理方式:
    可以通过重写ChannelInboundHandler接口的exceptionCaught方法来处理异常;
    因为进站异常消息和其它进站消息一样,都是从管道头流向管道尾部,因此为了保证管道中所有异常都被处理,一般在最后一个入站处理器那里处理异常;
    如果异常消息每被处理,netty会 Netty将会记录该异常没有被处理的事实;
    即 Netty 将会通过 Warning 级别的日志记录该异常到达了 ChannelPipeline 的尾端,但没有被处理,并尝试释放该异常;
public class InboundExceptionHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause) {
        cause.printStackTrace();    //打印异常信息
        ctx.close();                //关闭连接
    }
}
 
2)出站异常处理
1】使用ChannelFuture处理异常
每个出站操作都将返回一个 ChannelFuture;
注册到 ChannelFuture 的 ChannelFutureListener 将在操作完成时被通知该操作是成功了还是出错了;
 
代码:
ChannelFuture future = channel.write(someMessage);    //通过write方法写出一个出站事件
future.addListener(new ChannelFutureListener() {    //添加监视器
    @Override
    public void operationComplete(ChannelFuture f) {    //当写出事件完成时会获得异步通知
        if (!f.isSuccess()) {        //如果有异常该如何处理
            f.cause().printStackTrace();
            f.channel().close();
        }
    }
});
 
2】使用ChannelPromise处理异常
几乎所有的 ChannelOutboundHandler 上的方法都会传入一个 ChannelPromise的实例;
作为 ChannelFuture 的子类, ChannelPromise 也可以被分配用于异步通知的监听器;
除此之外ChannelPromise 还具有提供立即通知的可写方法:
     ChannelPromise setSuccess()    ->立即发送成功通知
    ChannelPromise setFailure(Throwable cause)    ->立即发送失败通知
 
代码:
public class OutboundExceptionHandler extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx, Object msg,ChannelPromise promise) {    //重写出站处理器的write方法,里面有个参数ChannelPromise
        promise.addListener(new ChannelFutureListener() {    //ChannelPromise是ChannelFuture 的子类,也可以添加监听器,用来获得write方法完成时的异步通知
            @Override
            public void operationComplete(ChannelFuture f) {
                if (!f.isSuccess()) {
                    f.cause().printStackTrace();
                    f.channel().close();
                }
            }
        });
    }
}
 
 
 
 
 
 
posted @ 2020-05-29 16:46  L丶银甲闪闪  阅读(585)  评论(0编辑  收藏  举报