Netty之ChannelPipeline和ChannelHandler(1)

7 篇文章 0 订阅

Netty的ChannelPipeline和ChannelHandler机制类似于Servlet和Filter过滤器,这类拦截器实际上是职责链模式的一种变形,主要是为了方便事件的拦截和用户业务逻辑的顶指。
Netty的Channel过滤器实现原理于Servlet Filter机制一致,她将Channel的数据管道抽象为ChannelPipeline,消息在ChannelPipeline中流动和传递。ChannelPipeline持有I/O事件拦截器ChannelHandler的链表,由ChannelHandler对I/O事件进行拦截和处理,可以方便地通过新增和删除ChannelHandler来实现对不同地业务逻辑定制,不需要对已有地ChannelHandler进行修改,能够实现对修改封闭和对扩展地支持。

ChannelPipeline是ChannelHandler地容器,它负责ChannelHandler地管理和事件拦截与调度。

ChannelPipeline的事件处理

ChannelPipeline对事件流地拦截和处理流程

  • 底层地SocketChannel read()方法读取ByteBuf,触发ChannelRead事件,由I/线程NioEventLoop调用ChannelPipeline地fireChannelRead(Object msg)方法,将消息(ByteBuf)传输到ChannelPipeline中;
  • 消息依次被HeadHandler、Channel Handler1、CannelHandler2…TailHandler拦截和处理,在这个过程中,任何ChannelHandler都可以中断当前地流程,结束消息地传递;
  • 调用ChannelHandlerContext地write方法发送消息,消息从TailHandler开始,途径ChannelHandlerN…ChannelHandler1、HeadHandler,最终被添加到消息发送缓冲区等待刷新和发送,在此过程中也可以中断消息地传递,例如当编码失败时,就需要中断流程,构造异常地Future返回。

Netty中事件分类:

1.inbound事件
inbount事件通常由I/O线程触发,例如TCP链路建立事件、链路关闭事件、读事件、异常通知事件等,它对应上图左半部分。
触发inbound事件地方法如下:

  • ChannelHandlerContext.fireChannelRegistreer():Channel注册事件;

  • ChannelHandlerContext.fireChannelActive():TCP链路建立成功,Channel激活事件;

  • ChannelHandlerContext.fireChannelRead(Object):读事件;

  • ChannelHandlerContext.fireChannelReadComplete():读操作完成通知事件;

  • ChannelHandlerContext.fireExceptionCaught(Throwable):异常通知事件;

  • ChannelHandlerContext.fireUserEventTriggered(Object):用户自定义事件;

  • ChannelHandlerContext.fireChannelWritabilityChanged():Channel地可写状态变化通知事件;

  • ChannelHandlerContext.fireChannelInactive():TCP连接关闭,链路不可用通知事件;
    2.outbound事件
    Outbound事件通常是由用户主动发起地网络I/O操作,例如用户发起地连接操作、绑定操作、消息发送等操作,它对应上图右半部分。
    触发outbount事件的方法如下:

  • ChannelHandlerContext.bind(SocketAddress,ChannelPromise):绑定本地地址事件;

  • ChannelHandlerContext.connect(SocketAddress,SocketAddress,ChannelPromise):连接服务端事件;

  • ChannelHandlerContext.write(Object,ChannelPromise):发送事件;

  • ChannelHandlerContext.flush():刷新事件;

  • ChannelHandlerContext.read():读事件;

  • ChannelHandlerContext.disconnect(ChannelPromise):断开连接事件;

  • ChannelhandlerContext.close(ChannelPromise):关闭当前Channel事件。

自定义拦截器:

ChannelPipeline通过ChannelHandler接口来实现事件的拦截和处理,由于ChannelHandler中的事件种类繁多,不同的ChannelHandler可能只需要关心其中的一个或者几个事件,所以,通常ChannelHandler只需要继承ChannelHandlerAdapter类覆盖自己关心的方法即可。
例如,下面的例子中展示了拦截Channel Active事件,打印TCP链路建立成功日志:

public class MyInboundHandler extends ChannelHandlerAdapter { 
	@Override
	public void channelActive( ChannelHandlerContext ctx) {
		System. out. println(" TCP connected!"); 
		ctx. fireChannelActive();
	}
}

下面的例子展示了如何在链路关闭的时候释放资源:

public class MyOutboundHandler extends ChannelHandlerAdapter{
	@Override
	public void close(ChannelHandlerContext ctx,ChannelPromise promise){
		System.out.println("TCP closing ...");
		Object.release();
		ctx.close(promise);
	}
}

构建pipeline

事实上用户不需要自己创建pipeline,因为使用ServerBootStrap或者Bootstrap启动服务端或者客户端时,Netty会为每个Channel连接创建一个独立的pipeline。对于使用者而言,只需要将自定义的拦截器加入到pipeline中即可。相关代码如下:

pipeline = ch.pipeline();
pipeline.addLast("decoder",new MyProtocolDecoder());
pipeline.addLast("encoder",new MyProtocolEncoder());

对于类似编解码这样的ChannelHandler,它存在先后顺序,例如MessageToMessageDecoder,在它之前往往需要有byteToMessageDecoder将ByteBuf解码为对象,然后对对象做二次解码得到最终的POJO对象。Pipeline支持指定位置添加或者删除拦截器,相关接口定义如下:
在这里插入图片描述
ChannelPipeline主要特性:
ChannelPipeline支持运行态动态的增加或者删除ChannelHandlert,在某些场景下这个特性非常实用。例如当业务高峰期需要对系统做拥塞保护时,就可以根据当前的系统时间进行判断,如果处于业务高峰期,则动态的将系统拥塞保护ChannelHandler添加到当前的ChannelPipeline中,当高峰期过去之后,就可以动态删除拥塞保护ChannelHandler了。
ChannelPipeline是线程安全的,这意味着N个业务线程可以并发的操作ChannelPipeline而不存在多线程并发问题。但是,Channelhandler却不是线程安全的,这意味着尽管ChannelPipeline是线程安全的,但是用户仍然需要自己保证ChannelHandler的线程安全。

ChannelPipeline源码分析

ChannelPipeline代码相对简单,它实际上是一个ChannelHandler的容器,内部维护了一个ChannelHandler的链表和迭代器,可以方便的实现ChannelHandler的查找、添加、删除和替换。
1.ChannelPipeline的类继承关系
ChannelPipeline类继承关系图
2.ChannelPipeline对ChannelHandler的管理
在这里插入图片描述
由于ChannelPipeline支持运行期动态修改,因此存在潜在的多线程并发访问场景,为保证ChannelPipeline的线程安全性,需要通过线程安全容器或锁来保证并发访问的安全,此处Netty直接使用了synchronized关键字,保证同步块内的所有操作的原子性。
代码如下:

 @Override
    public final ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            checkMultiplicity(handler);//校验ChannelHandler是否已添加过
            name = filterName(name, handler);//生成并校验ChannelHandler名称,若存在相同名称则抛出IllegalArgumentException异常

            newCtx = newContext(group, name, handler);

            addFirst0(newCtx);

            // If the registered is false it means that the channel was not registered on an eventLoop yet.
            // In this case we add the context to the pipeline and add a task that will call
            // ChannelHandler.handlerAdded(...) once the channel is registered.
            if (!registered) {
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
            }

            EventExecutor executor = newCtx.executor();
            //加入成功之后,缓存channelHandlerContext,发送新增ChannelHandlerContext通知消息
            if (!executor.inEventLoop()) {
                callHandlerAddedInEventLoop(newCtx, executor);
                return this;
            }
        }
        callHandlerAdded0(newCtx);
        return this;
    }

3.ChannelPipeline的inbound事件
当发生某个I/O事件的时候,例如链路建立连接、链路关闭、读取操作完成等都会产生一个事件,事件在pipeline中得到传播和处理,它是事件处理的总入口。由于网络I/O的事件有限,因此Netty对这些事件进行了统一抽象,Netty自身和用户的ChannelHandler会对感兴趣的事件进行拦截和处理。
pipeline中以fireXXX命名的方法都是从IO线程流向用户业务Handler的inbound事件,它们的实现因功能而异,但是处理步骤类似,总结如下:
(1)调用HeadHandler对应的fireXXX方法;
(2)执行事件相关的逻辑操作;
以fireChannelActive方法为例,调用head.fireChannelActive()之后,判断当前的Channel配置是否自动读取,若为自动读取则调用Channel的read方法,如下:

    @Override
    public final ChannelPipeline fireChannelActive() {
        AbstractChannelHandlerContext.invokeChannelActive(head);
        return this;
    }
    static void invokeChannelActive(final AbstractChannelHandlerContext next) {
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelActive();
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelActive();
                }
            });
        }
    }

4.ChannelPipeline的outbound事件
由用户线程或者代码发起的I/O操作被称为outbound事件,事实上inbound和outbound是Netty自身根据事件在pipeline中的流向抽象出来的属于,在其他NIO框架中并没有这个概念。
Pipeline本身并不直接进行I/O操作,在前面对Channel和Unsafe的介绍中我们知道最终都是由Unsafe和Channel来实现真正的I/O操作的。Pipeline负责将I/O事件通过TailHandler进行调度和传播,最终调用Unsafe的I/O方法纪念性I/O相关操作。
例如

//它直接调用TailHandler的connect方法,最终会调用到HeadHandler的connect方法,最终由HeadHandler调用Unsafe的connect方法发起真正的连接,pipeline仅仅负责事件的调度。
    @Override
    public final ChannelFuture connect(SocketAddress remoteAddress) {
        return tail.connect(remoteAddress);
    }

     @Override
        public void connect(
                ChannelHandlerContext ctx,
                SocketAddress remoteAddress, SocketAddress localAddress,
                ChannelPromise promise) {
            unsafe.connect(remoteAddress, localAddress, promise);
        }

《Netty权威指南》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值