精进篇:netty源码死磕5  - 揭开 ChannelHandler 的神秘面纱

目录

1. 前言

2. Handler在经典Reactor中的角色

3. Handler在Netty中的坐标位置

4. Netty中Handler的类型

1.1. ChannelInboundHandler入站处理器

1.2. ChannelOutboundHandler出站处理器

5. 揭开Pipeline的神秘面纱

6. Handler的上下文环境

7. Handler的注册

7.1. 第一步:包裹

7.2. 加入链表并注册完成回调事件

7.3. 回调添加完成事件

8. 小结

1. 前言

Reactor模式是Netty的基础和灵魂,掌握了经典的Reactor模式实现,彻底掌握Netty就事半功倍了。《Reactor模式(netty源码死磕3)》对Reactor模式的经典实现,进行了详细介绍。作为本文的阅读准备,可以去温习一下。

Reactor模式的两个重要的组件,一个是Reactor反应器,在Netty中的对应的实现是EventLoop,在文章《EventLoop(netty源码死磕4)》中,已经有了非常详细的介绍。

此文聚焦于Reactor模式的另一个重要的组成部分Handler。

2. Handler在经典Reactor中的角色

在Reactor经典模型中,Reactor查询到NIO就绪的事件后,分发到Handler,由Handler完成NIO操作和计算的操作。

Handler主要的操作为Channel缓存读、数据解码、业务处理、写Channel缓存,然后由Channel(代表client)发送到最终的连接终端。

3. Handler在Netty中的坐标

经典的Reactor模式,更多在于演示和说明,仅仅是有一种浓缩和抽象。

由于Netty更多用于生产,在实际开发中的业务处理这块,主要通过Handler来实现,所以Netty中在Handler的组织设计这块,远远比经典的Reactor模式实现,要纷繁复杂得多。

在分析Handler之前,首先回顾一下Netty中的Channel。在《EventLoop(netty源码死磕4)》中,已经有详细的说明。一个Netty Channel对应于一个Client连接,内部封装了一个Java NIO SelectableChannel 可查询通道。

再回到Handler。

Hander的根本使命,就是处理Channel的就绪事件,根据就绪事件,完成NIO处理和业务操作。比方Channel读就绪时,Hander就开始读;Channel写就绪时,Hander就开始写。

4. Netty中Handler的类型

从应用程序开发人员的角度来看,Netty的主要组件是ChannelHandler,所以,对ChannelHandler的分类,也是从应用开发的角度来的。

从应用程序开发人员的角度来看,数据有入站和出站两种类型。

这里的出站和入站,不是网络通信方面的入站和出站。而是相对于Netty Channel与Java NIO Channel而言的。

数据入站,指的是数据从底层的Java NIO channel到Netty的Channel。数据出站,指的是通过Netty的Channel来操作底层的 Java NIO chanel。

从入站和出战的角度出发,Netty中的ChannelHandler主要由两种类型,ChannelInboundHandler和ChannelOutboundHandler。

1.1. ChannelInboundHandler入站处理器

当Java NIO事件进站到Channel时,产生一的一系列事件将由ChannelHandler所对应的API处理。

当查询到Java NIO底层Channel的就绪事件时,通过一系列的ChannelInboundHandler处理器,完成底层就绪事件的处理。比方说底层连接建立事件、底层连接断开事件、从底层读写就绪事件等等。

啰嗦一下,入站(inbound)处理通常由底层Java NIO channel触发,主要事件如下:

1. 注册事件 fireChannelRegistered。

2. 连接建立事件 fireChannelActive。

3. 读事件和读完成事件 fireChannelRead、fireChannelReadComplete。

4. 异常通知事件 fireExceptionCaught。

5. 用户自定义事件 fireUserEventTriggered。

6. Channel 可写状态变化事件 fireChannelWritabilityChanged。

7. 连接关闭事件 fireChannelInactive。

1.2. ChannelOutboundHandler出站处理器

当需要Netty Channel需要操作Java NIO底层Channel时,通过一系列的ChannelOutboundHandler处理器,完成底层操作。比方说建立底层连接、断开底层连接、从底层Java NIO通道读入、写入底层Java NIO通道等。ChannelOutboundHandler是一个接口,主要操作如下图所示:

啰嗦一下,出站(inbound) Handler通常是Netty channel操作底层Java NIO channel,主要操作如下:

1. 端口绑定 bind。

2. 连接服务端 connect。

3. 写事件 write。

4. 刷新时间 flush。

5. 读事件 read。

6. 主动断开连接 disconnect。

7. 关闭 channel 事件 close。

至此,Netty中的两大处理器的类型,就已经说得很清楚了。

再说说Handler和Channel的关系。

打个比方,如果Hander是太阳系的行星,那么Channel就是太阳系的恒星。Hander的服务对象和公转的轴心,就是Channel。

这可能是最为不恰当的一个比方,但是说的是事实。

5. 揭开Pipeline的神秘面纱

一个Channel在数量上,肯定不止拥有一个Handler。 如何将杂乱无章的Handler,有序的组织起来呢?

来了一个Handler的装配器——Pipeline。

Pipeline是何方神圣呢?

先揭一下神秘面纱:

Netty中, 使用一个双向链表,将属于一个Channel的所有Handler组织起来,并且给这个双向链表封装在一个类中,再给这个类取了一个非常牛逼的名字,叫做ChannelPipeline。

为什么这个名字很牛逼呢?

实际上这里用了Java中一种非常重要的设计模式,Pipeline设计模式。后面将用专门的文章,来介绍这种牛逼模式。

回到主题:

一个Channel,仅仅一个ChannelPipeline。该pipeline在Channel被创建的时候创建。ChannelPipeline相当于是ChannelHandler的容器,它包含了一个ChannelHander形成的列表,且所有ChannelHandler都会注册到ChannelPipeline中。

6. Handler的上下文环境

在Netty的设计中,Handler是无状态的,不保存和Channel有关的信息。打个不恰当的比方,Handler就像国际雇佣军一样,谁给钱,给谁打仗。Handler的目标,是将自己的处理逻辑做得很完成,可以给不同的Channel使用。

与之不同的是,Pipeline是有状态的,保存了Channel的关系。

于是乎,Handler和Pipeline之间,需要一个中间角色,把他们联系起来。这个中间角色是谁呢?

它就是——ChannelHandlerContext 。

所以,ChannelPipeline 中维护的,是一个由 ChannelHandlerContext 组成的双向链表。这个链表的头是 HeadContext, 链表的尾是 TailContext。而无状态的Handler,作为Context的成员,关联在ChannelHandlerContext 中。在对应关系上,每个 ChannelHandlerContext 中仅仅关联着一个 ChannelHandler。

我们继续用源码说话。

Context的双向链表的主要代码,在 AbstractChannelHandlerContext类中。该类主要包含一个双向链表节点的前置和后置节点引用 prev、next,以及数据引用 handler,相当于链表数据结构中的 Node 节点。

部分关键源码节选如下:

  1. // ChannelHandler 首位指针
  2. final AbstractChannelHandlerContext head;
  3. final AbstractChannelHandlerContext tail;
  4. // pipeline 所属 channel
  5. private final Channel channel;
  6. private final ChannelFuture succeededFuture;
  7. private final VoidChannelPromise voidPromise;
  8. private final boolean touch = ResourceLeakDetector.isEnabled();
  9.  
  10. protected DefaultChannelPipeline(Channel channel) {
  11. this.channel = ObjectUtil.checkNotNull(channel, "channel");
  12. succeededFuture = new SucceededChannelFuture(channel, null);
  13. voidPromise = new VoidChannelPromise(channel, true);
  14.  
  15. tail = new TailContext(this);
  16. head = new HeadContext(this);
  17.  
  18. head.next = tail;
  19. tail.prev = head;
  20. }

7. Handler的注册

Handler是如何注册到Pipeline中的呢?

1.3. 第一步:包裹

加入到Pipeline之前,在Pipeline的基类DefaultChannelPipeline中,首先对Handler进行包裹。

代码如下:

  1. // 使用 AbstractChannelHandlerContext 包裹 ChannelHandler
  2.  
  3. private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
  4. return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
  5. }

1.4. 加入链表并注册完成回调事件

1. 构建了 AbstractChannelHandlerContext 节点,并加入到了链表尾部。

2. 如果 channel 尚未注册到 EventLoop,就添加一个任务到 PendingHandlerCallback 上,后续channel 注册完毕,再调用 ChannelHandler.handlerAdded。

3. 如果已经注册,马上调用 callHandlerAdded0 方法来执行 ChannelHandler.handlerAdded 注册完成的回调函数。

代码如下:

  1. @Override
  2. public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
  3. final AbstractChannelHandlerContext newCtx;
  4. synchronized (this) {
  5. // 检查,若不是 Sharable,而且已经被添加到其他 pipeline,则抛出异常
  6. checkMultiplicity(handler);
  7. // 构建 AbstractChannelHandlerContext 节点
  8. newCtx = newContext(group, filterName(name, handler), handler);
  9. // 添加到链表尾部
  10. addLast0(newCtx);
  11.  
  12. // registered 为 false 表示 channel 尚未注册到 EventLoop 上。
  13. // 添加一个任务到 PendingHandlerCallback 上,后续注册完毕,再调用 ChannelHandler.handlerAdded
  14. if (!registered) {
  15. newCtx.setAddPending();
  16. callHandlerCallbackLater(newCtx, true);
  17. return this;
  18. }
  19.  
  20. // registered 为 true,则立即调用 ChannelHandler.handlerAdded
  21. EventExecutor executor = newCtx.executor();
  22. // inEvent 用于判断当前线程是否是 EventLoop 线程。执行 ChannelHandler 时,必须在对应的 EventLoop 线程池中执行。
  23. if (!executor.inEventLoop()) {
  24. newCtx.setAddPending();
  25. executor.execute(new Runnable() {
  26. @Override
  27. public void run() {
  28. callHandlerAdded0(newCtx);
  29. }
  30. });
  31. return this;
  32. }
  33. }
  34. callHandlerAdded0(newCtx);
  35. return this;
  36. }
  37.  
  38. @Override
  39. public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
  40. final AbstractChannelHandlerContext newCtx;
  41. synchronized (this) {
  42. // 检查,若不是 Sharable,而且已经被添加到其他 pipeline,则抛出异常
  43. checkMultiplicity(handler);
  44. // 构建 AbstractChannelHandlerContext 节点
  45. newCtx = newContext(group, filterName(name, handler), handler);
  46. // 添加到链表尾部
  47. addLast0(newCtx);
  48.  
  49. // registered 为 false 表示 channel 尚未注册到 EventLoop 上。
  50. // 添加一个任务到 PendingHandlerCallback 上,后续注册完毕,再调用 ChannelHandler.handlerAdded
  51. if (!registered) {
  52. newCtx.setAddPending();
  53. callHandlerCallbackLater(newCtx, true);
  54. return this;
  55. }
  56.  
  57. // registered 为 true,则立即调用 ChannelHandler.handlerAdded
  58. EventExecutor executor = newCtx.executor();
  59. // inEvent 用于判断当前线程是否是 EventLoop 线程。执行 ChannelHandler 时,必须在对应的 EventLoop 线程池中执行。
  60. if (!executor.inEventLoop()) {
  61. newCtx.setAddPending();
  62. executor.execute(new Runnable() {
  63. @Override
  64. public void run() {
  65. callHandlerAdded0(newCtx);
  66. }
  67. });
  68. return this;
  69. }
  70. }
  71. callHandlerAdded0(newCtx);
  72. return this;
  73. }

1.5. 回调添加完成事件

添加完成后,执行回调方法如下:

  1. private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
  2. try {
  3. ctx.handler().handlerAdded(ctx);
  4. ctx.setAddComplete();
  5. } catch (Throwable t) {
  6. …….
  7. }

会执行handler的handlerAdded 方法,这是一个回调方法。添加完成后的回调代码,基本上写在这里。

8. 小结

至此,牛逼的Netty Handler和Netty Reactor 介绍完了。

对于Pipeline模式和基于Pipeline的Netty 入站和出站的事件传输机制,【疯狂创客圈】在后面的系列死磕文章,会做一个非常精彩的介绍。



无编程不创客,无案例不学习。疯狂创客圈,一大波高手正在交流、学习中!

疯狂创客圈 Netty 死磕系列 10多篇深度文章: 【博客园 总入口】  QQ群:104131248

Netty 源码(ChannelHandler 死磕)的更多相关文章

  1. Netty 源码 ChannelHandler(四)编解码技术

    Netty 源码 ChannelHandler(四)编解码技术 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 一.拆包与粘 ...

  2. Netty 源码 ChannelHandler(三)概述

    Netty 源码 ChannelHandler(三)概述 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 一.Channel ...

  3. ChannelHandler揭秘(Netty源码死磕5)

    精进篇:netty源码死磕5  揭开 ChannelHandler 的神秘面纱 目录 1. 前言 2. Handler在经典Reactor中的角色 3. Handler在Netty中的坐标位置 4. ...

  4. Pipeline模式(netty源码死磕6)

    精进篇:netty源码死磕6  巧夺天工--Pipeline模式揭秘 1. 巧夺天工--Pipeline模式揭秘 1.1. Pipeline模式简介 管道的发名者叫,Malcolm Douglas M ...

  5. JAVA NIO 简介 (netty源码死磕1.1)

    [基础篇]netty 源码死磕1.1:  JAVA NIO简介 1. JAVA NIO简介 Java 中 New I/O类库 是由 Java 1.4 引进的异步 IO.由于之前老的I/O类库是阻塞I/ ...

  6. Java NIO Buffer(netty源码死磕1.2)

    [基础篇]netty源码死磕1.2:  NIO Buffer 1. Java NIO Buffer Buffer是一个抽象类,位于java.nio包中,主要用作缓冲区.Buffer缓冲区本质上是一块可 ...

  7. EventLoop(netty源码死磕4)

    精进篇:netty源码  死磕4-EventLoop的鬼斧神工 目录 1. EventLoop的鬼斧神工 2. 初识 EventLoop 3. Reactor模式回顾 3.1. Reactor模式的组 ...

  8. Netty源码死磕一(netty线程模型及EventLoop机制)

    引言 好久没有写博客了,近期准备把Netty源码啃一遍.在这之前本想直接看源码,但是看到后面发现其实效率不高, 有些概念还是有必要回头再细啃的,特别是其线程模型以及EventLoop的概念. 当然在开 ...

  9. netty源码解解析(4.0)-17 ChannelHandler: IdleStateHandler实现

    io.netty.handler.timeout.IdleStateHandler功能是监测Channel上read, write或者这两者的空闲状态.当Channel超过了指定的空闲时间时,这个Ha ...

随机推荐

  1. Linux学习之二-Linux系统的目录结构

    Linux学习之二-Linux系统的目录结构 在Linux的根目录下,有很多的目录,但是需要记住,对于Linux而言,一切皆文件.因此此处的目录也是文件.用ls / 命令就能看到根目录下的各类不同的目 ...

  2. 【转】javascript 的类,原型,继承的理解

    原文: https://www.cnblogs.com/codernie/p/9098184.html ------------------------------------------------ ...

  3. ubuntu12.04下sun-java1.6-jdk配置

    1. 下载安装 2. 设置安装的默认程序 $ sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/jdk1.7.0_2 ...

  4. 倍福TwinCAT(贝福Beckhoff)基础教程2.2 TwinCAT常见类型使用和转换_数组

    声明和实例化数组的方法如下,你可以声明各种基本类型的数组 i: INT; array1: ARRAY [0..500] OF INT;   FOR i := 0 TO 5000 DO     arra ...

  5. vue笔记三(组件)

    十.组件 1.组件中的data为函数 2.props: 父组件向子组件传递数据 子组件:Child.vue <template> <span>{{ myMsg }}</s ...

  6. php装饰器

    <?php /* * 用一个类来装饰另一个类,动态的给一个对象增加一些额外功能,这些功能一般是在这个对象调用方法前或方法后 * 比如我们要给User类增加一个登陆日志的功能 */ // 抽象构件 ...

  7. react-native 封装 VedioPlayer 组件

    1.封装组件 src/components/VideoPlayer/index.js /** * 视频播放器 组件(VideoPlayer) */ import React, {Component} ...

  8. lodash 展平数组 flatten flattenDeep

    _.flatten(array) 向上一级展平数组嵌套 <!DOCTYPE html> <html lang="zh"> <head> < ...

  9. .mata. _root_ (转)

    HRegionServer 里面存放了很多的HRegion,而且每一个HRegion都有一个唯一标识(表名+开始主键+唯一ID),这个唯一标识符在每一个HRegion中都有存储. .mata.表存的数 ...

  10. Iterative (non-recursive) Merge Sort

    An iterative way of writing merge sort: #include <iostream> using namespace std; void merge(in ...