Netty源码分析第4章(pipeline)---->第2节: handler的添加
Netty源码分析第四章: pipeline
第二节: Handler的添加
添加handler, 我们以用户代码为例进行剖析:
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE, Delimiters.lineDelimiter()[0]));
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new SimpleHandler());
}
});
用过netty的小伙伴们肯定对这段代码不会陌生, 通过addLast, 可以添加编解码器和我们自定义的handler, 某一个事件完成之后可以自动调用我们handler预先定义的方法, 具体添加和调用是怎么个执行逻辑, 在我们之后的内容会全部学习到, 以后再使用这类的功能会得心应手
在这里, 我们主要剖析 ch.pipeline().addLast(new SimpleHandler()) 这部分代码的addLast()方法
首先通过channel拿到当前的pipline, 这个上一小节进行剖析过相信不会陌生
拿到pipeline之后再为其添加handler, 因为channel初始化默认创建的是DefualtChannelPipeline
我们跟到其addLast()方法中:
public final ChannelPipeline addLast(ChannelHandler... handlers) {
return addLast(null, handlers);
}
首先看到这里的参数其实是一个可变对象, 也就是可以传递多个handler, 这里我们只传递了一个
我们继续跟addLast:
public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
if (handlers == null) {
throw new NullPointerException("handlers");
}
//传多个参数的时候通过for循环添加
for (ChannelHandler h: handlers) {
if (h == null) {
break;
}
addLast(executor, null, h);
}
return this;
}
这里如果传入多个handler则会循环添加, 我们通常只添加一个
再继续跟到addLast()方法中去:
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
//判断handler是否被重复添加(1)
checkMultiplicity(handler);
//创建一个HandlerContext并添加到列表(2)
newCtx = newContext(group, filterName(name, handler), handler);
//添加HandlerContext(3)
addLast0(newCtx);
//是否已注册
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
//回调用户事件
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
//回调添加事件(4)
callHandlerAdded0(newCtx);
return this;
}
这部分代码比较长, 我们拆解为4个步骤:
1.重复添加验证
2.创建一个HandlerContext并添加到列表
3. 添加context
4. 回调添加事件
首先我们看第一步, 重复添加验证
我们跟到checkMultiplicity(handler)中:
private static void checkMultiplicity(ChannelHandler handler) {
if (handler instanceof ChannelHandlerAdapter) {
ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
if (!h.isSharable() && h.added) {
throw new ChannelPipelineException(
h.getClass().getName() +
" is not a @Sharable handler, so can't be added or removed multiple times.");
}
//满足条件设置为true, 代表已添加
h.added = true;
}
}
首先判断是不是ChannelHandlerAdapter类型, 因为我们自定义的handler通常会直接或者间接的继承该接口, 所以这里为true
拿到handler之后转换成ChannelHandlerAdapter类型, 然后进行条件判断
if (!h.isSharable() && h.added) 代表如果不是共享的handler, 并且是未添加状态, 则抛出异常:
我们可以跟到isSharable()方法中去:
public boolean isSharable() {
Class<?> clazz = getClass();
Map<Class<?>, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache();
Boolean sharable = cache.get(clazz);
if (sharable == null) {
//如果这个类注解了Sharable.class, 说明这个类会被多个channel共享
sharable = clazz.isAnnotationPresent(Sharable.class);
cache.put(clazz, sharable);
}
return sharable;
}
首先拿到当前handler的class对象
然后再从netty自定义的一个ThreadLocalMap对象中获取一个盛放handler的class对象的map, 并获取其value
如果value值为空, 则会判断是否被Sharable注解, 并将自身handler的class对象和判断结果存入map对象中, 最后返回判断结果
这说明了被Sharable注解的handler是一个共享handler
从这个逻辑我们可以判断, 共享对象是可以重复添加的
我们回到checkMultiplicity(handler)方法中:
如果是共享对象或者没有被添加, 则将ChannelHandlerAdapter的added设置为true, 代表已添加
剖析完了重复添加验证, 回到addLast方法中, 我们看第二步, 创建一个HandlerContext并添加到列表:
newCtx = newContext(group, filterName(name, handler), handler);
首先看filterName(name, handler)方法, 这个方法是判断添加handler的name是否重复
跟到filterName方法中:
private String filterName(String name, ChannelHandler handler) {
if (name == null) {
//没有名字创建默认名字
return generateName(handler);
}
//检查名字是否重复
checkDuplicateName(name);
return name;
}
因为我们添加handler时候, 不一定会会给handler命名, 所以这一步name有可能是null, 如果是null, 则创建一个默认的名字, 这里创建名字的方法我们就不往里跟了, 有兴趣的同学可以自己跟进去看
然后再检查名字是否重复
我们跟到checkDuplicateName(name)这个方法中:
private void checkDuplicateName(String name) {
//不为空
if (context0(name) != null) {
throw new IllegalArgumentException("Duplicate handler name: " + name);
}
}
这里有个context0(name)方法, 我们跟进去:
private AbstractChannelHandlerContext context0(String name) {
//遍历pipeline
AbstractChannelHandlerContext context = head.next;
while (context != tail) {
//发现name相同, 说明存在handler
if (context.name().equals(name)) {
//返回
return context;
}
context = context.next;
}
return null;
}
这里做的操作非常简单, 就是将pipeline中, 从head节点往下遍历HandlerContext, 一直遍历到tail, 如果发现名字相同则会认为重复并返回HandlerContext对象
我们回到addLast()方法中并继续看添加创建相关的逻辑:
newCtx = newContext(group, filterName(name, handler), handler)
filterName(name, handler)这步如果并没有重复则会返回handler的name
我们继续跟到newContext(group, filterName(name, handler), handler)方法中:
private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
}
这里我们看到创建了一个DefaultChannelHandlerContext对象, 构造方法的参数中, 第一个this代表当前的pipeline对象, group为null, 所以childExecutor(group)也会返回null, name为handler的名字, handler为新添加的handler对象
我们继续跟到DefaultChannelHandlerContext的构造方法中:
DefaultChannelHandlerContext(
DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
super(pipeline, executor, name, isInbound(handler), isOutbound(handler));
if (handler == null) {
throw new NullPointerException("handler");
}
this.handler = handler;
}
我们看到首先调用了父类的构造方法, 之后将handler赋值为自身handler的成员变量, HandlerConext和handler关系在此也展现了出来, 是一种组合关系
我们首先看父类的构造方法, 有这么两个参数:isInbound(handler), isOutbound(handler), 这两个参数意思是判断需要添加的handler是inboundHandler还是outBoundHandler
跟到isInbound(handler)方法中:
private static boolean isInbound(ChannelHandler handler) {
return handler instanceof ChannelInboundHandler;
}
这里通过是否实现ChannelInboundHandler接口来判断是否为inboundhandler
同样我们看isOutbound(handler)方法:
private static boolean isOutbound(ChannelHandler handler) {
return handler instanceof ChannelOutboundHandler;
}
通过判断是否实现ChannelOutboundHandler接口判断是否为outboundhandler
在跟到其父类AbstractChannelHandlerContext的构造方法中:
AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name,
boolean inbound, boolean outbound) {
this.name = ObjectUtil.checkNotNull(name, "name");
this.pipeline = pipeline;
this.executor = executor;
this.inbound = inbound;
this.outbound = outbound;
ordered = executor == null || executor instanceof OrderedEventExecutor;
}
一切都不陌生了, 因为我们tail节点和head节点创建的时候同样走到了这里
这里初始化了name, pipeline, 以及标识添加的handler是inboundhanlder还是outboundhandler
我们回到最初的addLast()方法中:
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
//判断handler是否被重复添加(1)
checkMultiplicity(handler);
//创建一个HandlerContext并添加到列表(2)
newCtx = newContext(group, filterName(name, handler), handler);
//添加HandlerContext(3)
addLast0(newCtx);
//是否已注册
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
//回调用户事件
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
//回调添加事件(4)
callHandlerAdded0(newCtx);
return this;
}
我们跟完了创建HandlerContext的相关逻辑, 我们继续跟第三步, 添加HandlerContext
我们跟进addLast0(newCtx)中:
private void addLast0(AbstractChannelHandlerContext newCtx) {
//拿到tail节点的前置节点
AbstractChannelHandlerContext prev = tail.prev;
//当前节点的前置节点赋值为tail节点的前置节点
newCtx.prev = prev;
//当前节点的下一个节点赋值为tail节点
newCtx.next = tail;
//tail前置节点的下一个节点赋值为当前节点
prev.next = newCtx;
//tail节点的前一个节点赋值为当前节点
tail.prev = newCtx;
}
这一部分也非常简单, 做了一个指针的指向操作, 将新添加的handlerConext放在tail节点之前, 之前tail节点的上一个节点之后, 熟悉双向链表的同学对此逻辑应该不会陌生, 如果是第一次添加handler, 那么添加后的结构入下图所示:

4-2-1
添加完handler之后, 这里会判断当前channel是否已经注册, 这部分逻辑我们之后再进行剖析, 我们继续往下走
之后会判断当前线程线程是否为eventLoop线程, 如果不是eventLoop线程, 就将添加回调事件封装成task交给eventLoop线程执行, 否则, 直接执行添加回调事件callHandlerAdded0(newCtx)
跟进callHandlerAdded0(newCtx):
private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
try {
ctx.handler().handlerAdded(ctx);
ctx.setAddComplete();
} catch (Throwable t) {
//忽略代码
}
}
我们重点关注这句
ctx.handler().handlerAdded(ctx);
其中ctx是我们新创建的HandlerContext, 通过handler()方法拿到绑定的handler, 也就是新添加的handler, 然后执行handlerAdded(ctx)方法, 如果我们没有重写这个方法, 则会执行父类的该方法
在ChannelHandlerAdapter类中定义了该方法的实现:
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
}
我们看到没做任何操作, 也就是如果我们没有重写该方法时, 如果添加handler之后将不会做任何操作, 这里如果我们需要做一些业务逻辑, 可以通过重写该方法进行实现
以上就是添加handler的有关的业务逻辑
Netty源码分析第4章(pipeline)---->第2节: handler的添加的更多相关文章
- Netty源码分析第4章(pipeline)---->第3节: handler的删除
Netty源码分析第四章: pipeline 第三节: handler的删除 上一小节我们学习了添加handler的逻辑操作, 这一小节我们学习删除handler的相关逻辑 如果用户在业务逻辑中进行c ...
- Netty源码分析第4章(pipeline)---->第4节: 传播inbound事件
Netty源码分析第四章: pipeline 第四节: 传播inbound事件 有关于inbound事件, 在概述中做过简单的介绍, 就是以自己为基准, 流向自己的事件, 比如最常见的channelR ...
- Netty源码分析第4章(pipeline)---->第5节: 传播outbound事件
Netty源码分析第五章: pipeline 第五节: 传播outBound事件 了解了inbound事件的传播过程, 对于学习outbound事件传输的流程, 也不会太困难 在我们业务代码中, 有可 ...
- Netty源码分析第4章(pipeline)---->第6节: 传播异常事件
Netty源码分析第四章: pipeline 第6节: 传播异常事件 讲完了inbound事件和outbound事件的传输流程, 这一小节剖析异常事件的传输流程 首先我们看一个最最简单的异常处理的场景 ...
- Netty源码分析第4章(pipeline)---->第7节: 前章节内容回顾
Netty源码分析第四章: pipeline 第七节: 前章节内容回顾 我们在第一章和第三章中, 遗留了很多有关事件传输的相关逻辑, 这里带大家一一回顾 首先看两个问题: 1.在客户端接入的时候, N ...
- Netty源码分析第4章(pipeline)---->第1节: pipeline的创建
Netty源码分析第四章: pipeline 概述: pipeline, 顾名思义, 就是管道的意思, 在netty中, 事件在pipeline中传输, 用户可以中断事件, 添加自己的事件处理逻辑, ...
- Netty源码分析第5章(ByteBuf)---->第10节: SocketChannel读取数据过程
Netty源码分析第五章: ByteBuf 第十节: SocketChannel读取数据过程 我们第三章分析过客户端接入的流程, 这一小节带大家剖析客户端发送数据, Server读取数据的流程: 首先 ...
- Netty源码分析第5章(ByteBuf)---->第4节: PooledByteBufAllocator简述
Netty源码分析第五章: ByteBuf 第四节: PooledByteBufAllocator简述 上一小节简单介绍了ByteBufAllocator以及其子类UnPooledByteBufAll ...
- Netty源码分析第5章(ByteBuf)---->第5节: directArena分配缓冲区概述
Netty源码分析第五章: ByteBuf 第五节: directArena分配缓冲区概述 上一小节简单分析了PooledByteBufAllocator中, 线程局部缓存和arean的相关逻辑, 这 ...
随机推荐
- 微信小程序的开发(一)
我现在在学习,微信小程序开发,刚刚看看一篇对我特别有用的博客文章,我就把摘抄过来了,好好的学习一下. 序言 开始开发应用号之前,先看看官方公布的「小程序」教程吧!(以下内容来自微信官方公布的「小程序」 ...
- JSP九大内置对象和四大作用域和Servlet的三大作用域对象
一.JSP九大内置对象:内置对象(又叫隐含对象,有9个内置对象):不需要预先声明就可以在脚本代码和表达式中随意使用 内置对象特点: 由JSP规范提供,不用编写者实例化. 通过Web容器实现和管理 所有 ...
- C# 数字证书 RSA加密解密 加签验签
KeyValuePair<string, string> keyPair = Encrypter.CreateRSAKey(); string privateKey = keyPair.V ...
- 进程调度之FCFS算法(先来先运行算法)
#include<stdio.h> #define PNUMBER 5//进程个数 #define SNUMBER 3//资源种类个数 //资源的种类,三种 char stype[SNUM ...
- CentOS 6.4 编译安装 gcc 4.8.1
今天在isocpp上看到“GCC 4.8.1 released, C++11 feature complete”这个消息,非常兴奋.终于有一个全面支持C++11语言特性的编译器了! 当然了,gcc仅仅 ...
- Ext4文件系统架构分析(三) ——目录哈希、扩展属性与日志
struct dx_root Htree的内部节点: struct dx_node Htree 树根和节点中都存在的 Hash map: struct dx_entry 1.20 扩展属性EA 扩展属 ...
- 升级时针对Source oracle home 不存在解决办法
针对于oracle升级从11.2.0.4升级至12.1.0.1,遇到的问题解决. 运行/oracle/app/product/12.1.0.1/dbhome_1/bin/dbua 后 在选择数据库ho ...
- CH1102 火车进出栈问题(高精/卡特兰数)
描述 一列火车n节车厢,依次编号为1,2,3,-,n.每节车厢有两种运动方式,进栈与出栈,问n节车厢出栈的可能排列方式有多少种. 输入格式 一个数,n(n<=60000) 输出格式 一个数s表示 ...
- swoft orm中的坑(针对实体类的属性名称和数据库字段不相等)
最近在用swoft的orm,发现了一些问题: 首先看下实体类的定义 它的属性名称和所映射的数据库字段名不一致,这个就会导致蛋疼的问题,首先,在我们使用orm的时候,应该使用哪个字段? 我直接说结论,在 ...
- HO引擎近况20180111
明明想着10号更新了,怎么就忘了呢? 公司的项目又被砍了,几个同事被开,我又转到另外一下组了,难道真的是我到哪哪黄? 快过年了,今年公司年会就在附近,太好了不用想怎么编理由请假事了,还有可能中个奖品, ...