ChannelPipeline 源码分析(二十三)

今天开始分析Netty源码:ChannelPipeline 源码分析

深入 Netty
从前面对 Netty 的使用我们看到,在运行的时候, Netty 中的组件一起为基于 Netty 的网络通信做出了贡献,所以下面我们来一一分析 Netty 中的组件,包括 ChannelPipeline 、 ChannelHandlerContext、 ChannelHandler EventLoop Channel Unsafe ByteBuf 等等,并且了解当服务器启动、当各种事件发生时 Netty 内部的运行机制。
一、ChannelPipeline 源码分析  简介
Netty ChannelPipeline ChannelHandler 机制类似于 Servlet Filter 过滤器,这类拦截器实际上是职责链模式的一种变形,主要是为了方便事件的拦截和用户业务逻辑的定制。Servlet Filter 是 J2EE Web 应用程序级的 Java 代码组件,它能够以声明的方式插入到HTTP 请求响应的处理过程中,用于拦截请求和响应,以便能够查看、提取或以某种方式操 作正在客户端和服务器之间交换的数据。拦截器封装了业务定制逻辑,能够实现对 Web 应用程序的预处理和事后处理。过滤器提供了一种面向对象的模块化机制,用来将公共任务封装到可插入的组件中。这 些组件通过 Web 部署配置文件 ( web.xml )进行声明,可以方便地添加和删除过滤器,无须改动任何应用程序代码或 JSP 页面,由 Servlet 进行动态调用。通过在请求 / 响应链中使用过 滤器,可以对应用程序(而不是以任何方式替代)的 Servlet JSP 页面提供的核心处理进行补充,而不破坏 Servlet JSP 页面的功能。由于是纯 Java 实现,所以 Servlet 过滤器具有跨平台的可重用性,使得它们很容易被部署到任何符合 Servlet 规范的 J2EE 环境中。Netty 的 Channel 过滤器实现原理与 Servlet Filter 机制一致,它将 Channel 的数据管道抽象为 ChannelPipeline ,消息在 ChannelPipeline 中流动和传递。 ChannelPipeline 持有 IO 事件拦截器 ChannelHandler 的链表,由 ChannelHandler IO 事件进行拦截和处理,可以方便地通过新增和删除 ChannelHandler 来实现不同的业务逻辑定制,不需要对已有的ChannelHandler 进行修改,能够实现对修改封闭和对扩展的支持。
二、ChannelPipeline 的事件处理
1、ChannelPipeline ChannelHandler 的容器,它负责 ChannelHandler 的管理和事件拦截与调度。
(1 )底层的 SocketChannel read() 方法读取 ByteBuf ,触发 ChannelRead 事件,由 IO 线程NioEventLoop 调用 ChannelPipeline fireChannelRead(Object msg) 方法,将消息 (ByteBuf )传输到 ChannelPipeline 中。
(2 )消息依次被 ChannelPipeline 中的各个入站 Handler 拦截和处理,在这个过程中,任何 ChannelHandler 都可以中断当前的流程,结束消息的传递。
(3 )调用 write 方法发送消息,消息被出站 Handler 拦截和处理,最终被添加到消息发送缓冲区中等待刷新和发送,在此过程中也可以中断消息的传递,例如当编码失败时,就需要中断流程,构造异常的 Future 返回。
Netty 中的事件分为 inbound 事件和 outbound 事件。 inbound 事件通常由 IO 线程触发, 例如 TCP 链路建立事件、链路关闭事件、读事件、异常通知事件等。
2、触发 inbound 事件的方法如下:
(1) ChannelHandlerContext.fireChannelRegistered(): Channel 注册事件 ;
(2)ChannelHandlerContext.fireChannelActive():TCP 链路建立成功, Channel 激活事件 ;
( 3)ChannelHandlerContext.fireChannelRead(Object): 读事件 ;
(4) ChannelHandlerContext.fireChannelReadComplete(): 读操作完成通知事件 ;
( 5) ChannelHandlerContext.fireExceptionCaught(Throwable): 异常通知事件 ;
( 6) ChannelHandlerContext.fireUserEventTriggered(Object):用户自定义事件 :
( 7)ChannelHandlerContext.fireChannelWritabilityChanged(): Channel 的可写状态变化通知事件;
( 8)ChannelHandlerContext.fireChannelInactive():TCP 连接关闭,链路不可用通知事件。
Outbound 事件通常是由用户主动发起的网络 IO 操作,例如用户发起的连接操作、绑定操作、消息发送等操作。
3、触发 outbound 事件的方法如下 :
( 1) ChanneHandlerContext.bind(Socket.Address, ChannelPromise): 绑定本地地址事件 ; ( 2)ChannelHandlerContext.connect(SocketAddress SocketAddress ChannelPromise): 连接服务端事件;
( 3 ChannelHandlerContext.write(Object, ChannelPromise): 发送事件 ;
( 4)ChannelHandlerContext.flush(): 刷新事件 ;
(5)ChannelHandlerContext.read(): 读事件 ;
(6 ChannelHandlerContext.disconnect(ChannelPromise): 断开连接事件;
(7)ChannelHandlerContext.close(ChannelPromise): 关闭当前 Channel 事件。
三、构建 pipeline 和特性
根据我们在前面对 Netty 的使用,我们知道,我们不需要自己创建 pipeline ,因为使用ServerBootstrap 或者 Bootstrap 启动服务端或者客户端时, Netty 会为每个 Channel 连接创建一个独立的 pipeline 。对于使用者而言,只需要将自定义的拦截器加入到 pipeline 中即可。ChannelPipeline 支持运行态动态的添加或者删除 ChannelHandler ,在某些场景下这个特性非常实用。例如当业务高峰期需要对系统做拥塞保护时,就可以根据当前的系统时间进行判断,如果处于业务高峰期,则动态地将系统拥塞保护 ChannelHandler 添加到当前的ChannelPipeline 中,当高峰期过去之后,就可以动态删除拥塞保护 ChannelHandler 了。ChannelPipeline 是线程安全的 , 这意味着 N 个业务线程可以并发地操作 ChannelPipeline 而不存在多线程并发问题。但是,ChannelHandler 却不是线程安全的,这意味着尽管ChannelPipeline 是线程安全的,但是用户仍然需要自己保证 ChannelHandler 的线程安全。
1、类关系图
 
 
2、DefaultChannelPipeline
可以看见, ChannelPipeline 的实现其实只有一个 DefaultChannelPipeline ,所以我们对ChannelPipeline 的了解就集中在这个类上。
3、对 ChannelHandler 的管理
ChannelPipeline ChannelHandler 的管理容器,必然要负责 ChannelHandler 的查询、添加、替换和删除,所以它里面的方法很多都是和管理 ChannelHandler 有关的,比如 add 系列、remove 系列、 replace 系列。 ChannelPipeline 内部维护了一个 ChannelHandler 的链表和迭代器,所以从本质上来说, 上述的管理操作基本上就是对链表的操作。比如add 操作源码:
 
@Override
public final ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
        checkMultiplicity(handler);
        name = filterName(name, handler);

        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();
        if (!executor.inEventLoop()) {
            newCtx.setAddPending();
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    callHandlerAdded0(newCtx);
                }
            });
            return this;
        }
    }
    callHandlerAdded0(newCtx);
    return this;
}

但是 ChannelHandler 本身并不是链表上的元素,所以在具体实现上是把 ChannelHandler 包装进 ChannelHandlerContext 的实例 DefaultChannelHandlerContext,然后把 ChannelHandlerContext 作为元素来组成链表。 由于 ChannelPipeline 支持运行期动态修改,因此存在两种潜在的多线程并发访问场景。

 
1) IO 线程和用户业务线程的并发访问 ;
2) 、用户多个线程之间的并发访问。
为了保证 ChannelPipeline 的线程安全性,需要通过线程安全容器或者锁来保证并发访问的安全,此处 Netty 直接使用了 synchronized 关键字,保证同步块内的所有操作的原子性。在加入链表之前,Netty 还会检查该 Handler 是否已经被添加过及其名字是否有重复,如果该 Handler 不是共享的,而且被添加过抛出异常,如果名字重复,也会抛出异常。 完成链表操作后,后面的部分基本上做的就是一件事,执行方法 callHandlerAdded0 ,只是根据条件不同进行同步或者异步执行,callHandlerAdded0 的主要作用是在 handler 添加被加入链表之后做一些额外工作,Netty 本身对这个方法的实现,在 ChannelHandlerAdapter 类中,是个空操作
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    // NOOP
}
这里如果我们需要做一些业务逻辑, 可以通过重写该方法进行实现。
 
4、inbound 系列
当发生某个 I/O 事件的时候,例如链路建立、链路关闭、读取操作完成等,都会产生一个事件,事件在 pipeline 中得到传播和处理,它是事件处理的总入口。由于网络 IO 相关的事件有限, 因此 Netty 对这些事件进行了统一抽象 , Netty 自身和用户的 ChannelHandler 会对感兴趣的事件进行拦截和处理。pipeline 中以 fireXXX 命名的方法都是从 IO 线程流向用户业务 Handler inbound 事件,它们的实现因功能而异,但是处理步骤类似。
总结如下:
(1) 调用 HeadHandler 对应的 fireXXX 方法 ;
(2 )执行事件相关的逻辑操作。
5、outbound 系列
由用户线程或者代码发起的 IO 操作被称为 outbound 事件 , 事实上 inbound outbound 是 Netty 自身根据事件在 pipeline 中的流向抽象出来的术语,在其他 NIO 框架中并没有这个概念。outbound 事件包括发起绑定、发起连接、发起读写、刷新数据、发起关闭、发起断连等等。Pipeline 本身并不直接进行 IO 操作,最终都是由 Unsafe Channel 来实现真正的 IO 操作的。Pipeline 负责将 IO 事件通过 TailHandler 进行调度和传播,最终调用 Unsafe IO 方法进行 I/O 操作。
到此,ChannelPipeline 源码分析分析完毕,下篇分析ChannelHandlerContext相关源码,敬请期待!
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寅灯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值