近日学习Netty,在看书和实践的时候对于书上只言片语的那些话不是十分懂,导致尝试写例子的时候遭遇各种不顺,比如decoder和encoder还有HttpObjectAggregator的添加顺序,研究了一番之后和大家分享一下自己的理解,希望后来人可以少走弯路。

模型浅析

简单描述下ChannelHandler的存储模型,ChannelHandler在ChannelPipeline中主要以AbstractChannelHandlerContext为基类存储,存储的数据结构为链表,传进去的ChannelHandler都会转化为DefaultChannelHandlerContext来存储在ChannelPipeline里,ChannelPipeline主要的实现为DefaultChannelPipeline。

DefaultChannelPipeline

DefaultChannelPipeline使用双向链表储存所有AbstractChannelHandlerContext,定义如下:

public class DefaultChannelPipeline implements ChannelPipeline {
static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelPipeline.class);
private static final String HEAD_NAME = generateName0(DefaultChannelPipeline.HeadContext.class);
private static final String TAIL_NAME = generateName0(DefaultChannelPipeline.TailContext.class);
private static final FastThreadLocal<Map<Class<?>, String>> nameCaches = new FastThreadLocal<Map<Class<?>, String>>() {
protected Map<Class<?>, String> initialValue() throws Exception {
return new WeakHashMap();
}
};
final AbstractChannelHandlerContext head;//双向链表,头指针
final AbstractChannelHandlerContext tail;//双向链表,尾指针
private final Channel channel;
private final ChannelFuture succeededFuture;
private final VoidChannelPromise voidPromise;
private final boolean touch = ResourceLeakDetector.isEnabled();
private Map<EventExecutorGroup, EventExecutor> childExecutors;
private Handle estimatorHandle;
private boolean firstRegistration = true;
private DefaultChannelPipeline.PendingHandlerCallback pendingHandlerCallbackHead;
private boolean registered;
……
}

在调用addLast(First,Before)等方法添加ChannelHandler到ChannelPipeline时,实际上是new了一个DefaultChannelHandlerContext对象插入到链表中:

public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized(this) {
checkMultiplicity(handler);
newCtx = this.newContext(group, this.filterName(name, handler), handler);//第一个参数为eventgroup,第二个参数为通过方法获取的channelhandler名称,第三个为channelhandler
this.addLast0(newCtx);//构造完成后,last使用前插法插入链表尾部,first使用后插法插入链表头部
if(!this.registered) {
newCtx.setAddPending();
this.callHandlerCallbackLater(newCtx, true);
return this;
} EventExecutor executor = newCtx.executor();
if(!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
public void run() {
DefaultChannelPipeline.this.callHandlerAdded0(newCtx);
}
});
return this;
}
} this.callHandlerAdded0(newCtx);
return this;
}

DefaultChannelHandlerContext

final class DefaultChannelHandlerContext extends AbstractChannelHandlerContext {
private final ChannelHandler handler; DefaultChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
super(pipeline, executor, name, isInbound(handler), isOutbound(handler));//调用父类构造方法创建context
if(handler == null) {
throw new NullPointerException("handler");
} else {
this.handler = handler;//保存handler引用
}
} public ChannelHandler handler() {
return this.handler;
} private static boolean isInbound(ChannelHandler handler) {
return handler instanceof ChannelInboundHandler;
} private static boolean isOutbound(ChannelHandler handler) {
return handler instanceof ChannelOutboundHandler;
}
}

Handler传递顺序

现在我们知道Pipeline里实际是一个context的链表,现在我们来看看fireChannelRead和write的传递顺序

fireChannelRead

调用fireChannelRead方法时,调用该方法的context会从自己开始在链表中根据自己的next指针来寻找下一个注册(invoke)的handler去处理事件,代码如下:

public ChannelHandlerContext fireChannelRead(Object msg) {
invokeChannelRead(this.findContextInbound(), msg);//将查找到的context传递进入执行channelRead方法,里面还有一些eventLoop的判断
return this;
}
private AbstractChannelHandlerContext findContextInbound() {
AbstractChannelHandlerContext ctx = this; do {
ctx = ctx.next;
} while(!ctx.inbound);//从当前context开始,查找到下一个为inbound的handler,所以说outbound和inbound的插入顺序与执行顺序或执行成功与否没有任何关系,只与最后链表的结果有关,并且当handler过多时会影响遍历速度 return ctx;
}

write

调用write方法时,调用该方法的context会从自己开始在链表中根据自己的pre指针来寻找上一个注册(invoke)的handler去处理事件,顺序与fireChannelRead相反,代码如下:

private void write(Object msg, boolean flush, ChannelPromise promise) {
AbstractChannelHandlerContext next = this.findContextOutbound();//查找到上一个outBoundContext
Object m = this.pipeline.touch(msg, next);
EventExecutor executor = next.executor();
if(executor.inEventLoop()) {
if(flush) {
next.invokeWriteAndFlush(m, promise);
} else {
next.invokeWrite(m, promise);
}
} else {
Object task;
if(flush) {
task = AbstractChannelHandlerContext.WriteAndFlushTask.newInstance(next, m, promise);
} else {
task = AbstractChannelHandlerContext.WriteTask.newInstance(next, m, promise);
} safeExecute(executor, (Runnable)task, promise, m);
} }
private AbstractChannelHandlerContext findContextOutbound() {
AbstractChannelHandlerContext ctx = this; do {
ctx = ctx.prev;
} while(!ctx.outbound); return ctx;
}

补充

如果write不调用context的write方法,而是调用context.channel().write(),则会直接调用使用pipeline的tail指针开始向前遍历outboundhandler执行,如果有特殊执行需求时可以考虑使用这种调用方法。相关源码就不贴了,有兴趣的小伙伴可以自己去看。

总结

到这里,Handler执行顺序已经介绍完毕了,总结为:

  1. 对于channelInboundHandler,总是会从传递事件的开始,向链表末尾方向遍历执行可用的inboundHandler。

  2. 对于channelOutboundHandler,总是会从write事件执行的开始,向链表头部方向遍历执行可用的outboundHandler。

举例说明如下代码:

ch.pipeline().addLast(new OutboundHandler1());
ch.pipeline().addLast(new OutboundHandler2());
ch.pipeline().addLast(new InboundHandler1());
ch.pipeline().addLast(new InboundHandler2());

链表中的顺序为head->out1->out2->in1->in2->tail

那么Inbound的执行顺序为read->in1->in2

在Inbound执行write后,outbound执行顺序为out1<-out2<-write

  1. 所以实际使用中,如果添加的顺序不好,很可能会意外跳过某些inbount或者outbound。建议实际使用上,先通过addFirst插入所有outBound再通过addLast插入所有inBound这样inBound与outBound的插入顺序与执行顺序完全一致,且不会出现跳过的情况。

    很多源码中的习惯都是只使用addLast或者addFirst插入,然后顺序在心中,具体方法见仁见智,保证顺序不错就行
  2. 所以一些统一编码解码的handler,例如ssl,httpcodec,最好是按照顺序放在链表头!这样才会保证进出都会执行到并且业务逻辑可以正常插入

Netty学习:ChannelHandler执行顺序详解,附源码分析的更多相关文章

  1. Android应用AsyncTask处理机制详解及源码分析

    1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个知识点.前面我们分析了Handler异步机制原理(不了解的可以阅读我的<Android异步消息处理机 ...

  2. Spring Boot启动命令参数详解及源码分析

    使用过Spring Boot,我们都知道通过java -jar可以快速启动Spring Boot项目.同时,也可以通过在执行jar -jar时传递参数来进行配置.本文带大家系统的了解一下Spring ...

  3. 【转载】Android应用AsyncTask处理机制详解及源码分析

    [工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果] 1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个 ...

  4. 线程池底层原理详解与源码分析(补充部分---ScheduledThreadPoolExecutor类分析)

    [1]前言 本篇幅是对 线程池底层原理详解与源码分析  的补充,默认你已经看完了上一篇对ThreadPoolExecutor类有了足够的了解. [2]ScheduledThreadPoolExecut ...

  5. Java SPI机制实战详解及源码分析

    背景介绍 提起SPI机制,可能很多人不太熟悉,它是由JDK直接提供的,全称为:Service Provider Interface.而在平时的使用过程中也很少遇到,但如果你阅读一些框架的源码时,会发现 ...

  6. SpringMVC异常处理机制详解[附带源码分析]

    目录 前言 重要接口和类介绍 HandlerExceptionResolver接口 AbstractHandlerExceptionResolver抽象类 AbstractHandlerMethodE ...

  7. ArrayList用法详解与源码分析

    说明 此文章分两部分,1.ArrayList用法.2.源码分析.先用法后分析是为了以后忘了查阅起来方便-- ArrayList 基本用法 1.创建ArrayList对象 //创建默认容量的数组列表(默 ...

  8. [转]SpringMVC拦截器详解[附带源码分析]

      目录 前言 重要接口及类介绍 源码分析 拦截器的配置 编写自定义的拦截器 总结 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:ht ...

  9. SpringMVC拦截器详解[附带源码分析]

    目录 前言 重要接口及类介绍 源码分析 拦截器的配置 编写自定义的拦截器 总结 总结 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:h ...

  10. Servlet容器Tomcat中web.xml中url-pattern的配置详解[附带源码分析]

    目录 前言 现象 源码分析 实战例子 总结 参考资料 前言 今天研究了一下tomcat上web.xml配置文件中url-pattern的问题. 这个问题其实毕业前就困扰着我,当时忙于找工作. 找到工作 ...

随机推荐

  1. Unity GameObject

    GameObject 游戏对象 GameObject是unity所有实体的基类 gameObject 获取当前脚本所挂载的游戏对象 一般来说,在属性视图中能看到或修改的属性,我们同样可以在脚本中获取并 ...

  2. django DRF理解

    django restframework(DRF) 最近的开发过程当中,发现restframework的功能很强大,所以尝试解读了一下源码,写篇博客分享给大家,有错误的地方还请各位多多指出 视图部分 ...

  3. Jupyter Notebook在多个虚拟环境配置与使用

    1 问题描述 使用Anaconda配置了包括Pytorch.Tensorflow等多个虚拟环境后,依然无法使用Jupyter Notebook选择不同的虚拟环境运行代码,问题如下图所示. 2 解决方法 ...

  4. Web服务器-并发服务器-协程 (3.4.2)

    @ 目录 1.分析 2.代码 关于作者 1.分析 随着网站的用户量越来愈多,通过多进程多线程的会力不从心 使用协程可以缓解这一问题 只要使用gevent实现 2.代码 from socket impo ...

  5. WinForm引用ActiveX组件,对Com组件的学习

    1.WinForm引用Adobe PDF Reader 工作中写WinForm程序经常会引用第三方的组件,包括引用Com组件,做了一个桌面程序需要展示PDF,看了些其它的开源组件对PDF的兼容性都不是 ...

  6. 【收藏】关于元数据(Metadata)和元数据管理,这是我的见过最全的解读!

    本文主要从元数据的定义.作用.元数据管理现状.管理标准和元数据管理功能等方面讲述了我对元数据(Metadata)和元数据管理的认知及理解. 元数据管理 一.元数据的定义 按照传统的定义,元数据(Met ...

  7. 制作3D小汽车游戏(下)

    书接上回,这一节我们分模块说一说怎么写一个这样的游戏 1. 初始化场景.相机和渲染器 这几乎是绘制three必须做的事情,我们有两套场景和相机,一个是主场景和相机,另一个是小地图的场景和相机(用来俯视 ...

  8. 微信小程序--相对路径和绝对路径

    相对路径: ../表示返回访问上一级目录 如:../../components/like/index 绝对路径: 以"/"开头 表示从根目录开始寻找文件 如:/components ...

  9. Android OpenGL ES 开发

    OpenGL(Open Graphics Library) 是开放图形库,是一个跨平台的图形 API.OpenGL ES(OpenGL for Embedded System)是专为移动端提供的一个子 ...

  10. springMVC生成pdf文件

    pom.xml文件配置=== <!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf --> <dependenc ...