1.前言

 本节介绍Netty中第三个重要的概念——Handler,这个在前两节都提到了,尤其是Channel和Handler联系紧密。handler本身的设计非常简单,但是所起到的作用却很大,Netty中对于handler的实现非常多(handler是控制socket io的各个生命周期的业务实现,netty实现了很多种协议,自然有很多handler类)。本节并不关心各种不同功能的handler具体实现,主要讲解handler的设计和作用及使用方法。

2.主要概念

2.1 ChannelHandler

 这个图可以看到ChannelHandler的基本结构,其有两个子类接口,ChannelInboundHandler和ChannelOutboundHandler。这也就是我之前常常提到的handler分成in和out类型,针对不同操作的方法。

  handlerAdded():handler被添加到channel的时候执行,这个动作就是由pipeline的添加handler方法完成的。对于服务端,在客户端连接进来的时候,就通过ServerBootstrapAcceptor的read方法,为每一个channel添加了handler。该方法对于handler而言是第一个触发的方法。

  handlerRemoved():handler被移除channel的时候执行,这个动作在pipeline中也存在,基本上是在channel断开的时候会进行这一步。

  exceptionCaught():捕获channel生命周期中异常处理的类,该方法被移动到ChannelInboundHandler中了。已经废弃。

 注解Sharable表明该handler可以被多个pipeline重复使用。

2.2 ChannelInboundHandler

 该接口主要负责的是业务逻辑,主要接口方法如下:

  channelRegistered():在channel注册到线程池的时候会被触发。

  channelUnregistered():在channel关闭的时候触发。

  channelActive():registered完成之后且channel处理active状态,首次注册状态。主要见AbstractChannel的register0(promise)方法。

  channelnactive():unregistered之前执行,主要见AbstractChannel的deregister方法。

  channelRead():这个主要见pipeline的fireChannelRead方法,其被channel在获取到数据的阶段进行调用,进而触发到handler的channelRead方法。

  channelReadComplete():这个和上面read方法一样,fireChannelReadComplete方法,被channel运行过程中read过程完成会进行调用。

  userEventTriggered():这个也是由pipeline提供的方法作为入口fireUserEventTriggered,这个就是触发一个事件了,以IdleStateHandler为例,其一般作为心跳检测事件,放入线程池执行,判断空闲就会触发该方法,传导到各个handler。

  channelWritabilityChanged():这个入口也在pipeline,但是好像没有怎么用到,channel并没有调用这个方法,一般也没怎么用该方法。

  exceptionCaught():这个入口同样在pipeline,被channel的读取过程抛出异常时触发,当然不只这一个地方。

 上述方法追根溯源都是通过pipeline来触发整个执行链的,后面讲到context时会详细说明一下这个过程。只需要知道入口在这个地方即可。

2.3 ChannelOutboundHandler

 比起in类型handler倾向于处理业务逻辑,out类型的handler更倾向于处理连接逻辑。下面是该类型的接口方法定义:

  bind():绑定端口的操作

  connect():连接远程地址

  disconnect():断开连接

  close():端口绑定的端口

  deregister():取消注册到线程池中

  read():设置监听读取事件

  write():写入数据的操作

  flush():刷新缓冲区,立刻发送。

 上述接口都是和连接相关的处理,而且这些都是通过pipeline的相关方法触发的,最终调用的是tail对象。实际上这个handler比较鸡肋,原因channel那节其实已经说明过,大部分实现类都做不了什么,最终都交给headContext对象调用unsafe相关方法由其处理了。大部分handler只对write方法进行了处理,其他都直接交给其他的handler处理了。我印象中之前Netty5好像要取消in和out的区别,outhandler太鸡肋了(Netty5已经放弃开发,官方的说法是forkjoin框架开发很复杂,还没准备好开发)。

2.4 ChannelHandlerContext

 handlerContext在之前我们就简要提到过,这个和pipeline联合使用,管理handler链。handler本身没有持有顺序关系,都是通过handlerContext完成的。handlerContext自身会通过配合handler造成顺着构成的链式顺序调用下去,这里会仔细说明一下这个过程。

 接口方法就不一一介绍了,看到的都是一些熟悉的方法,针对业务的起始调用fireXXX(),其它的方法就不进行介绍了。handlerContext和pipeline一样实现类很少,基本使用的就是DefaultChannelHandlerContext,其继承自AbstractChannelHandlerContext,大部分方法也都是在抽象父类中。下面具体介绍一下是如何工作的:

 还是回到pipeline的构造handler链的过程:

  1. protected DefaultChannelPipeline(Channel channel) {
  2. this.channel = ObjectUtil.checkNotNull(channel, "channel");
  3. succeededFuture = new SucceededChannelFuture(channel, null);
  4. voidPromise = new VoidChannelPromise(channel, true);
  5.  
  6. tail = new TailContext(this);
  7. head = new HeadContext(this);
  8.  
  9. head.next = tail;
  10. tail.prev = head;
  11. }

 pipeline初始化的时候构建了tail和head两个handler,这两个是特殊的,位置不能替换的。

  1. public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
  2. final AbstractChannelHandlerContext newCtx;
  3. synchronized (this) {
  4. checkMultiplicity(handler);
  5.  
  6. newCtx = newContext(group, filterName(name, handler), handler);
  7.  
  8. addLast0(newCtx);
  9.  
  10. // If the registered is false it means that the channel was not registered on an eventloop yet.
  11. // In this case we add the context to the pipeline and add a task that will call
  12. // ChannelHandler.handlerAdded(...) once the channel is registered.
  13. if (!registered) {
  14. newCtx.setAddPending();
  15. callHandlerCallbackLater(newCtx, true);
  16. return this;
  17. }
  18.  
  19. EventExecutor executor = newCtx.executor();
  20. if (!executor.inEventLoop()) {
  21. newCtx.setAddPending();
  22. executor.execute(new Runnable() {
  23. @Override
  24. public void run() {
  25. callHandlerAdded0(newCtx);
  26. }
  27. });
  28. return this;
  29. }
  30. }
  31. callHandlerAdded0(newCtx);
  32. return this;
  33. }
  34.  
  35.   private void addLast0(AbstractChannelHandlerContext newCtx) {
  36.   AbstractChannelHandlerContext prev = tail.prev;
  37.   newCtx.prev = prev;
  38.   newCtx.next = tail;
  39.   prev.next = newCtx;
  40.   tail.prev = newCtx;
  41.   }

 先判断这个handler有没有被其他pipeline加载过,是否是sharable类型,进行相关设置。通过handler创建context。然后插入到tail前面,后面就是添加handler之后触发的方法,触发handlerAdd方法。通过addLast0方法,可以看见context中的参数prev和next被初始化了。也就是说通过当前的context能够找到前一个和后一个context了。下面抽出handler接口的其中一个方法,我们来研究一下handler链是怎么执行的。

  1. public ChannelHandlerContext fireChannelActive() {
  2. invokeChannelActive(findContextInbound());
  3. return this;
  4. }
  5.  
  6. static void invokeChannelActive(final AbstractChannelHandlerContext next) {
  7. EventExecutor executor = next.executor();
  8. if (executor.inEventLoop()) {
  9. next.invokeChannelActive();
  10. } else {
  11. executor.execute(new Runnable() {
  12. @Override
  13. public void run() {
  14. next.invokeChannelActive();
  15. }
  16. });
  17. }
  18. }
  19.  
  20. private void invokeChannelActive() {
  21. if (invokeHandler()) {
  22. try {
  23. ((ChannelInboundHandler) handler()).channelActive(this);
  24. } catch (Throwable t) {
  25. notifyHandlerException(t);
  26. }
  27. } else {
  28. fireChannelActive();
  29. }
  30. }
  31.  
  32.   private AbstractChannelHandlerContext findContextInbound() {
  33.   AbstractChannelHandlerContext ctx = this;
  34.   do {
  35.   ctx = ctx.next;
  36.   } while (!ctx.inbound);
  37.   return ctx;
  38.   }

 handler的任何一个方法,在context中都是由三个这样的方法来构成链式执行的。static方法都是给pipeline调用的,其入参就是head,handler链的头结点。调用了invokeChannelActive()方法,这里就是我们的handler方法相关内容被触发了。乍一看好像执行完了就没了啊,这个地方要接下去就需要handler配合了。随便找一个handler,比如ChannelInboundHandlerAdapter,其相关方法都调用了ctx.fireChannelActive();  这里就接上了,ctx的fireChannelActive不就是回到了invokeChannelActive()方法了。其入参就不再是当前的context,而是通过findContextInbound来找到下一个in类型的handler,这也说明这个接口是针对in类型的接口。所以一个基本的循环就是ctx.invokeChannelActive(ctx) ->ctx.invokeChannelActive() -> hander.channelActive()->ctx.fireChannelActive(ctx)->findContextInbound()->ctx.invokeChannelActive(ctx)。这么个循环链,起点就是pipeline的invokeChannelActive(head),终点就是handler.channelActive()没有调用ctx.channel的时候,最后的tailContext就是没有执行任何操作,所以执行到这链路就断了。

3.后记

 本节对handler的一个基本内容进行了说明,主要讲解了handlerContext的执行过程,明确了调用链的入口在于pipeline,通过pipeline和context的控制,保证链的执行。channel的生命周期控制pipeline就能控制整个handler的执行。下图给个具体说明:

  上图综合了前几章的内容,整个核心调用过程就是这些了,但是还没有设置线程问题,所以启动类以及线程相关没有加入到图中,下一章将对Netty的线程模型进行分析。

Netty核心概念(6)之Handler的更多相关文章

  1. Netty In Action中文版 - 第三章:Netty核心概念

            在这一章我们将讨论Netty的10个核心类.清楚了解他们的结构对使用Netty非常实用.可能有一些不会再工作中用到.可是也有一些非经常常使用也非常核心,你会遇到. Bootstrap ...

  2. Netty核心概念(10)之内存管理

    1.前言 之前的章节已经将启动demo中能看见的内容都分析完了,Netty的一个整体样貌都在第8节线程模型最后给的图画出来了.这些内容解释了Netty为什么是一个异步事件驱动的程序,也解释了Netty ...

  3. Netty核心概念(8)之Netty线程模型

    1.前言 第7节初步学习了一下Java原本的线程池是如何工作的,以及Future的为什么能够达到其效果,这些知识对于理解本章有很大的帮助,不了解的可以先看上一节. Netty为什么会高效?回答就是良好 ...

  4. Netty核心概念(7)之Java线程池

    1.前言 本章本来要讲解Netty的线程模型的,但是由于其是基于Java线程池设计而封装的,所以我们先详细学习一下Java中的线程池的设计.之前也说过Netty5被放弃的原因之一就是forkjoin结 ...

  5. Netty核心概念(5)之Channel

    1.前言 上一节讲了Netty的第一个关键启动类,启动类所做的一些操作,和服务端的channel固定的handler执行过程,谈到了不管是connect还是bind方法最终都是调用了channel的相 ...

  6. Netty核心概念(4)之Bootstrap

    1.前言 第三节介绍了Netty的一些基本概念,此节介绍Netty的第一个概念Bootstrap——启动类.Netty中服务端和客户端的启动类是不一样的,这个不要搞错了,类都在bootstrap包下. ...

  7. Netty核心概念

    一个Netty程序始于Bootstrap类,Bootstrap类是Netty提供的一个可以通过简单配置来设置或“引导”程序的一个重要的类.Netty中设计了Handlers来处理特定的"ev ...

  8. Netty核心概念(9)之Future

    1.前言 第7节讲解JAVA的线程模型中就说到了Future,并解释了为什么可以主线程可以获得线程池任务的执行后结果,变成一种同步状态.秘密就在于Java将所有的runnable和callable任务 ...

  9. Ext JS 6学习文档–第2章–核心概念

    核心概念 在下一章我们会构建一个示例项目,而在这之前,你需要学习一些在 Ext JS 中的核心概念,这有助于你更容易理解示例项目.这一章我们将学习以下知识点: 类系统,创建和扩展类 事件 Ext JS ...

随机推荐

  1. jdk1.7和1.8共存的问题(默认1.7)

    参考https://www.cnblogs.com/fxmemory/p/7234848.html 电脑上有了jdk1.7,环境变量配的是1.7,后来再安装了个1.8,结果在cmd-->java ...

  2. DataGrid组件

    <?xml version="1.0" encoding="utf-8"?><s:WindowedApplication xmlns:fx=& ...

  3. GitBash入门

    转载自:http://www.cnblogs.com/randomsteps/p/5415116.html 作为一个初学者,我是跟着廖学峰老师的官方博客学习,这里只是做个笔记,哈哈,关于git的历史. ...

  4. ROM初始化HEX文件

    intel hex格式 记录格式 Intel HEX由任意数量的十六进制记录组成.每个记录包含5个域,它们按以下格式排列: :llaaaatt[dd...]cc 每一组字母对应一个不同的域,每一个字母 ...

  5. Spring bean加载2--FactoryBean情况处理

    Spring bean加载2--FactoryBean情况处理 在Spring bean加载过程中,每次bean实例在返回前都会调用getObjectForBeanInstance来处理Factory ...

  6. 玩转git分支

    搞个代码的管理工具,居然不弄上分支啥的东西.这简直太low了.尤其是在使用了传说中得很牛X的Git的时候,尤其显得low.拿着青龙偃月刀当烧火棍子使,关公知道了还不重反人间教育你!? 远程分支 要说分 ...

  7. android注解处理技术APT

    APT(Annotation Processing Tool)是java的注解处理技术,它对源代码文件进行检测找出其中的Annotation,根据注解和注解处理器和相应的apt自动生成代码. Anno ...

  8. .Net socket服务器编程之为何也高效

    说到Socket编程,肯定大部分人举手c,c++.可惜现在已没有机会去追随并达到写服务器的水平,所以将就下还是考虑c#版的Socket服务器吧. 经过一番查询,试用.一些数据和事实还是浮出水面,同时对 ...

  9. Ajax请求php返回json对象数据中包含有数字索引和字符串索引,在for in循环中取出数据的顺序问题

    //php中的数组数据格式 Array ( [all] => Array ( [title] => ALL [room_promotion_id] => all ) [best_av ...

  10. BCP 基本语法

    copy from:http://msdn.microsoft.com/zh-cn/library/ms162802.aspx bcp [database_name.] schema.{table_n ...