netty源码解析(4.0)-10 ChannelPipleline的默认实现--事件传递及处理

19 篇文章 0 订阅
18 篇文章 0 订阅

 

  事件触发、传递、处理是DefaultChannelPipleline实现的另一个核心能力。在前面在章节中粗略地讲过了事件的处理流程,本章将会详细地分析其中的所有关键细节。这些关键点包括:

  • 事件触发接口和对应的ChannelHandler处理方法。
  • inbound事件的传递。 
  • outbound事件的传递。
  • ChannelHandler的eventExecutor的分配。

  

  事件的触发方法和处理方法

  netty提供了三种触发事件的方式:通过Channel触发,通过ChannelPipleline触发,通过ChannelHandlerContext触发。

  

  Channel触发

  在netty源码解解析(4.0)-2 Chanel的接口设计这一章中,列出了Channel触发事件的所有方法。Channel定义的所有事件触发方法中,都是用来触发outbound事件的,只有read方法比较特殊,它直接触发outbound方法,如果能读到数据则会触发inbound方法。下面是Channel的事件触发方法,和ChannelHandler事件处理方法的对应关系。

  outbound事件

Channel方法ChannelOutboundHandler方法
bindbind(ChannelHandlerContext, SocketAddress, ChannelPromise)  
connectconnect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)
disconnectdisconnect(ChannelHandlerContext, ChannelPromise)
closeclose(ChannelHandlerContext, ChannelPromise)
deregisterderegister(ChannelHandlerContext, ChannelPromise)
writewrite(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
flushflush(ChannelHandlerContext ctx)
writeAndFlush先调用write然后调用flush
readread(ChannelHandlerContext)

  inbound事件

Channel方法ChannelInboundHandler方法
readchannelRead(ChannelHandlerContext, Object)
channelReadComplete(ChannelHandlerContext)

  Channel通过调用ChannelPipleline的同名方方法触发事件,以下是AbstractChannel实现的bind的方法

1 @Override
2 public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
3  return pipeline.bind(localAddress, promise);
4 }

  其他方法的实现和bind类似。

  

  ChannelPipleline触发

  在netty源码解解析(4.0)-8 ChannelPipeline的设计这一章中,列出了所有触发事件的方法。 ChannelPipleline的outbound事件的触发方法和处理方法的对应关系和Channel一样,这里就不再重复罗列。下面是inbound事件的触发方法和ChannelHandler事件处理方法的对应关系:

  inbound事件

ChannelPipleline方法ChannelInboundHandler方法
fireChannelRegisteredchannelRegistered(ChannelHandlerContext) 
fireChannelUnregisteredchannelUnregistered(ChannelHandlerContext)
fireChannelActivechannelActive(ChannelHandlerContext)
fireChannelInactivechannelInactive(ChannelHandlerContext)
fireChannelReadchannelRead(ChannelHandlerContext, Object)
fireChannelReadCompletechannelReadComplete(ChannelHandlerContext)
fireExceptionCaughtexceptionCaught(ChannelHandlerContext, Throwable)
fireUserEventTriggereduserEventTriggered(ChannelHandlerContext, Object)
fireChannelWritabilityChangedchannelWritabilityChanged(ChannelHandlerContext)

   在DefaultChannelPipleline实现中,事件触发是通过调用AbstractChannelHandlerContext的方法实现的。inbound事件的触发方式是调用对应的invokeXXX静态方法。例如: fireChannelRegistered方法会调用invokeChannelRegistered静态方法:

1 @Override
2 public final ChannelPipeline fireChannelRegistered() {
3      AbstractChannelHandlerContext.invokeChannelRegistered(head);
4      return this;
5 }

  这里会把链表的头作为输入参数,表明inbound事件是从链表头开始处理。其他inbound事件触发方法的实现和这个类似。

  outbound事件的触发方式是调用AbstractChannelHandlerContext的同名方法,例如bind的方法的实现:

1     @Override
2     public final ChannelFuture bind(SocketAddress localAddress) {
3         return tail.bind(localAddress);
4     }

  这调用链表尾的方法,表明outbind事件从链表尾开始处理。其他outbound事件的触发方法和这个类似。

 

  ChannelHandlerContext触发

  Channel的事件触发方法会调用DefaultChannelPipleline的触发方法,而DefaultChannelPipleline的触发方法调用AbstractChannelHandlerContext的触发方法。所以,不论是Channel还是ChannelPipleline,他们的事件触发能力都是AbstractChannelHandlerContext提供的,因此ChannelHandlerContext事件触发方法和ChannelHandler事件处理方法的对应关系和Channel,ChannelPipleline是一样的。

  

  三种触发方法的差异

  Channel只能触发outbound事件,事件从链表的tail开始,传递到head。ChannelPipleline既可以触发outbound事件,又能触发inbound事件,outbound事件的处理和Channel触发一样,inbound事件的从链表的head开始,传递到tail。ChannelHandlerContext触发方式最为灵活,如果调用ChannelHandlerContext的实例触发事件,outbound事件从这个实例的节点开始向head方向传递,inbound事件从这个实例的节点开始向tail传递,此外还可以调用AbstractChannelHandlerContext提供的静态方法从链表中的任意一个节点开始触发可处理事件。总结起来就是,Channel和ChannelPipleline触发的事件只能从链表的head或tail节点开始触发和传递事件,而ChannelHanderContext可以从链表中任何一个节点触发和传递事件。

 

  事件的传递

  事件传递的功能在AbstractChannelHandlerContext,这个类的定义如下:

    abstract class AbstractChannelHandlerContext extends DefaultAttributeMap implements ChannelHandlerContext

  inbound事件的触发和传递

  每个inbound事件的触发传递实现包含3个方法,一个普通方法fireXXX,一个静态方法invokeXXX, 和一个普通方法invokeXXX。每一次inbound事件传递就是一轮fire-invoke-invoe的调用。下面是channelRegisterd事件的相关的代码。

复制代码

 1     @Override
 2     public ChannelHandlerContext fireChannelRegistered() {
 3         invokeChannelRegistered(findContextInbound());
 4         return this;
 5     }
 6 
 7     static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
 8         EventExecutor executor = next.executor();
 9         if (executor.inEventLoop()) {
10             next.invokeChannelRegistered();
11         } else {
12             executor.execute(new Runnable() {
13                 @Override
14                 public void run() {
15                     next.invokeChannelRegistered();
16                 }
17             });
18         }
19     }
20 
21     private void invokeChannelRegistered() {
22         if (invokeHandler()) {
23             try {
24                 ((ChannelInboundHandler) handler()).channelRegistered(this);
25             } catch (Throwable t) {
26                 notifyHandlerException(t);
27             }
28         } else {
29             fireChannelRegistered();
30         }
31     }

复制代码

  这三个方法各有不同的职责:

  • fireChannelRegistered调用findContextInbound找到链表上下一个ChannelInboundHandler类型的节点,并把这个节点作为参数传给静态方法invokeChannelRegistered。
  • 静态invokeChannelRegistered负责调用普通invokeChannelRegistered方法,并确保这个方法在eventExecutor中调用。
  • 普通invokeChannelRegistered负责调用handler对应的事件处理方法,处理异常。如果这个handler对应的handlerAdded方法没有完成调用,这handler还不能处理事件,跳过这节点,继续下一轮fire-invoke-invoke循环。

  在普遍invoveChannelRegistered中,正常情况下会调用handler的事件处理方法,这里是handler的channelRegistered方法。如果事件处理方法没有调用对应的fire方法,那么这个事件的传递就算终止了。所以事件传递还需要handler的配合。

  inbound事件传递的关键实现在findContextInbound中,这个方法是实现如下:

复制代码

1     private AbstractChannelHandlerContext findContextInbound() {
2         AbstractChannelHandlerContext ctx = this;
3         do {
4             ctx = ctx.next;
5         } while (!ctx.inbound);
6         return ctx;
7     }

复制代码

  这里使用next向后遍历节点,使用inbound属性判断节点持有的handler是否ChannelInboundHandler类型,直到找到一个合适的节点为止。如果没找到,则返回最后一个节点。这样就对链表中最后一个节点提出了一些特殊的要求:必须是持有ChannelInboundHandler的handler并且;并且要负责终止事件传递。DefaultPipleline.TailContext类的实现就满足了这两点要求。

  

  outbound事件的触发和传递

  每个outbound事件的触发和传递包含两点方法: XXX, invokeXXX。 下面以bind事件为例看看outbound事件的触发和传递:

复制代码

 1     @Override
 2     public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
 3         if (localAddress == null) {
 4             throw new NullPointerException("localAddress");
 5         }
 6         if (isNotValidPromise(promise, false)) {
 7             // cancelled
 8             return promise;
 9         }
10 
11         final AbstractChannelHandlerContext next = findContextOutbound();
12         EventExecutor executor = next.executor();
13         if (executor.inEventLoop()) {
14             next.invokeBind(localAddress, promise);
15         } else {
16             safeExecute(executor, new Runnable() {
17                 @Override
18                 public void run() {
19                     next.invokeBind(localAddress, promise);
20                 }
21             }, promise, null);
22         }
23         return promise;
24     }
25 
26     private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
27         if (invokeHandler()) {
28             try {
29                 ((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
30             } catch (Throwable t) {
31                 notifyOutboundHandlerException(t, promise);
32             }
33         } else {
34             bind(localAddress, promise);
35         }
36     }

复制代码

 

  bind方法调用findContextOutbound找到链表上下一个持有ChannelOutboundHandler类型handler的节点,并且确保invokeBind方法在eventExecutor中执行。invokeBind方法负责调用handler对应的事件处理方法,这里是调用handler的bind方法。handler的bind方法中需要调用节点bind方法,这个事件才能继续传递下去。

  outbound事件传递的关键实现在findContextOutbound中,这个方法的实现如下:

复制代码

1     private AbstractChannelHandlerContext findContextOutbound() {
2         AbstractChannelHandlerContext ctx = this;
3         do {
4             ctx = ctx.prev;
5         } while (!ctx.outbound);
6         return ctx;
7     }

复制代码

 

  这里使用链表的prev向前遍历,使用outbound属性判断节点持有的handler是否ChannelOutboundHandler类型,直到找到一个为止。如果没找到,将会返回链表头的节点。这样对链表头的节点也提出了特殊的要求:它持有的handler必须是ChannelOutboundHandler类型。

  

  链表节点持有的handler类型

  在事件的传递和处理过程中,必须把inbound事件交给ChannelInboundChandler类型的handler处理,把outbound事件交给ChannelOutboundChandler类型的handler处理。为了判断handler类型,定义了两个boolean类型的属性: inbound, outbound。inbound==true表示handler是ChannelInboundHandler类型, outbound==true表示handler是ChannelOutboundHandler类型。这两个值在AbstractChannelHandlerContext构造方法中初始化,初始化值来自输入的参数。DefaultChannelHandlerContext在构造方法中把这两个参数的值传入。

复制代码

1     DefaultChannelHandlerContext(
2             DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
3         super(pipeline, executor, name, isInbound(handler), isOutbound(handler));
4         if (handler == null) {
5             throw new NullPointerException("handler");
6         }
7         this.handler = handler;
8     }

复制代码

  使用isInbound的的值设置inbound,isOutbound的值设置outbound。这两方法只是简单的使用了instanceof运算符。

复制代码

1     private static boolean isInbound(ChannelHandler handler) {
2         return handler instanceof ChannelInboundHandler;
3     }
4 
5     private static boolean isOutbound(ChannelHandler handler) {
6         return handler instanceof ChannelOutboundHandler;
7     }

复制代码

 

  为ChannelHandler分配eventExecutor

  把一个channleHandler添加到ChannelPipleline中时,ChannelPipleline会给它分配一个eventExecutor, 它的所有的事件处理方法都会在这个executor中执行。如果使用带group参数的add方法,executor会从group中取,否则会把channel的eventLoop当成这个handler的executor使用。 从group中分配execuor的操作在创建持有handler的链表节点时完成:

    private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
        return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
    }

 

  childExecutor方法负责从group中取出一个executor分配给handler:

复制代码

 1     private EventExecutor childExecutor(EventExecutorGroup group) {
 2         if (group == null) {
 3             return null;
 4         }
 5         Boolean pinEventExecutor = channel.config().getOption(ChannelOption.SINGLE_EVENTEXECUTOR_PER_GROUP);
 6         if (pinEventExecutor != null && !pinEventExecutor) {
 7             return group.next();
 8         }
 9         Map<EventExecutorGroup, EventExecutor> childExecutors = this.childExecutors;
10         if (childExecutors == null) {
11             // Use size of 4 as most people only use one extra EventExecutor.
12             childExecutors = this.childExecutors = new IdentityHashMap<EventExecutorGroup, EventExecutor>(4);
13         }
14         // Pin one of the child executors once and remember it so that the same child executor
15         // is used to fire events for the same channel.
16         EventExecutor childExecutor = childExecutors.get(group);
17         if (childExecutor == null) {
18             childExecutor = group.next();
19             childExecutors.put(group, childExecutor);
20         }
21         return childExecutor;
22     }

复制代码

  实际的分配操作要稍微复杂一些。取决于channel的ChannelOption.SINGLE_EVENTEXECUTOR_PER_GROUP设置,如果没有设置这个选项或设置成true,  表示每个channelPipleline只能从一个group中分配一个executor,  这是默认行为,实现代码是地9行-19行,这种情况下每一个使用了同一个group的handler,都会被分配到同一个executor中。如果把这个选择设置成false,这是简单地从group中取出一个executor,实现代码是地7行,这种情况下,每一个使用了同一个group的handler被均匀地分配到group中的每一个executor中。

  如果没有指定group,会在地3行退出,这里没有分配executor。这种情况会在AbstractChannelHandlerContext的executor方法中得到妥善处理:

复制代码

1     @Override
2     public EventExecutor executor() {
3         if (executor == null) {
4             return channel().eventLoop();
5         } else {
6             return executor;
7         }
8     }

复制代码

  第4行,处理了没分配executor的情况,调用channel的eventLoop方法得到channel的eventLoop。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值