ChannelPipeline源码与高级拦截过滤器模式分析

ChannelPipeline简介

ChannelPipeline是处理或拦截一个Channel的入站事件和出站操作的ChannelHandlers的列表。ChannelPipeline实现了拦截过滤器模式的高级形式,让用户完全控制事件的处理方式以及pipeline中的ChannelHandlers之间的交互方式。

ChannelPipeline创建

每一个channel有它自己的pipeline,并且当一个新的channel被创建时自动创建pipeline。

事件在pipeline中的流动

下图描述了通常情况下,ChannelPipeline中ChannelHandlers是如何处理I/O事件的。
I/O事件由ChannelInboundHandler或ChannelOutboundHandler处理并通过调用ChannelHandlerContext中定义的事件传播方法将其转发给最近的handler。
例如 ChannelHandlerContext#fireChannelRead(Object) 和
ChannelHandlerContext#write(Object)。
在这里插入图片描述
在web应用中使用的拦截过滤器通常都会拦截处理请求和响应,而ChannelPipeline的拦截过滤器模式是一种更加细粒度的实现。
入站事件由入站handlers按照自底向上的方向处理,如图左侧所示。一个入站handler通常处理处理由图底部的I/O线程生成的入站数据。入站数据通常从远程对端读取通过实际的input操作例如SocketChannel#read(ByteBuffer),如果入站事件传递到顶部的入站handler后面,它将被悄悄地丢弃。

出站事件由出站handlers自顶向下处理,如图右侧所示。出站handler通常生成或转换出站流,如写请求。如果一个出站事件超出出站handler的底部,它将被channel关联的I/O线程处理,这个I/O线程通常执行实际的output操作,例如SocketChannel#write(ByteBuffer)。

例如,假设我们创建了以下pipeline:
ChannelPipeline p = …;
p.addLast(“1”, new InboundHandlerA());
p.addLast(“2”, new InboundHandlerB());
p.addLast(“3”, new OutboundHandlerA());
p.addLast(“4”, new OutboundHandlerB());
p.addLast(“5”, new InboundOutboundHandlerX());

3 和 4 没有 implement ChannelInboundHandler, 因此 实际的入站事件顺序是: 1, 2, and 5。
1 和 2 没有 implement ChannelOutboundHandler, 因此 实际的出站事件顺序是: 5, 4, and 3。

下面的示例展示了事件传播通常是如何完成的:

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


public class MyOutboundHandler extends ChannelOutboundHandlerAdapter {
@Override
public void close(ChannelHandlerContext ctx,
ChannelPromise promise) {
        System.out.println("Closing ..");
         ctx.close(promise);
     }
 }
线程安全

一个ChannelHandler可以在任何时候被添加和删除,因为ChannelPipeline是线程安全的。例如,你可以插入一个加密handler当敏感信息即将被交换时,在交换后将其移除。

DefaultChannelPipeline

DefaultChannelPipeline实现了ChannelPipeline接口。
在这里插入图片描述
在DefaultChannelPipeline的构造方法中,会保存channel的引用,以及创建一个TailContext和HeadContext实例。这是维护一种双向链表的形式。
看下ChannelPipeline addLast(…)的实现:
在这里插入图片描述
这边会为要加进去的handler创建一个context实例,然后再将这个context加入到链表中:
在这里插入图片描述
所以pipeline中实际保存的是一个又一个的context对象,而通过context对象可以获取与之关联的handler、channel以及pipeline对象,因此context对象是handler与pipeline交互的纽带。

但是这个时候channel还没有注册在eventLoop上,所以还不会执行callHandlerAdd0(newCtx)方法;在这种情况下,执行到callHandlerCallbackLater(newCtx, true)看一看:
在这里插入图片描述
在这边会将新new出来的PendingHandlerAddedTask实例保存到DefaultChannelPipeline的成员变量pendingHandlerCallbackHead中。
在这里插入图片描述
所有的挂起任务,会在channel注册时,通过调用callHandlerAddedForAllHandlers()进行处理。详见channel注册分析

PendingHandlerAddedTask继承了PendingHandlerCallback,而PendingHandlerCallback实现了Runnable接口。
在这里插入图片描述
在这里插入图片描述
可以发现,PendingHandlerCallback也维护了一个链表的结构,所以是将挂起的Task添加到链表中。

先来看下callHandlerAdded0(newCtx)做了什么:
在这里插入图片描述
在此完成对于我们加入到pipeline的handler中handlerAdded(ChannelHandlerContext ctx)方法的触发调用。

接下来分析Netty一个重要的组件ChannelInitializer:在这里插入图片描述
一旦initChannel(C ch)方法返回,当前ChannelInitializer实例就会从pipeline中移除,这是怎么做到的呢?
在这里插入图片描述
结合上面分析,可以看到,当ChannelInitializer实例被添加到pipeline后,并且channel被注册到eventLoop上,ChannelInitializer的handlerAdded(ChannelHandlerContext ctx)方法会被调用。在initChannel(© ctx.channel())这行代码中会执行用户实现的具体initChannel方法,将一个个handler添加进pipeline中。最后,执行remove(ctx)会将当前ChannelInitializer实例从pipeline中移除。

那么用户自定义的ChannelInitializer实例又是在什么时机被加入到pipeline中的呢?
详见ServerBootstrapAcceptor源码分析

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值