ChannelHandler揭秘(Netty源码死磕5)
精进篇: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 节点。
部分关键源码节选如下:
// ChannelHandler 首位指针
final AbstractChannelHandlerContext head;
final AbstractChannelHandlerContext tail;
// pipeline 所属 channel
private final Channel channel;
private final ChannelFuture succeededFuture;
private final VoidChannelPromise voidPromise;
private final boolean touch = ResourceLeakDetector.isEnabled(); protected DefaultChannelPipeline(Channel channel) {
this.channel = ObjectUtil.checkNotNull(channel, "channel");
succeededFuture = new SucceededChannelFuture(channel, null);
voidPromise = new VoidChannelPromise(channel, true); tail = new TailContext(this);
head = new HeadContext(this); head.next = tail;
tail.prev = head;
}
7. Handler的注册
Handler是如何注册到Pipeline中的呢?
1.3. 第一步:包裹
加入到Pipeline之前,在Pipeline的基类DefaultChannelPipeline中,首先对Handler进行包裹。
代码如下:
// 使用 AbstractChannelHandlerContext 包裹 ChannelHandler private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
}
1.4. 加入链表并注册完成回调事件
1. 构建了 AbstractChannelHandlerContext 节点,并加入到了链表尾部。
2. 如果 channel 尚未注册到 EventLoop,就添加一个任务到 PendingHandlerCallback 上,后续channel 注册完毕,再调用 ChannelHandler.handlerAdded。
3. 如果已经注册,马上调用 callHandlerAdded0 方法来执行 ChannelHandler.handlerAdded 注册完成的回调函数。
代码如下:
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
// 检查,若不是 Sharable,而且已经被添加到其他 pipeline,则抛出异常
checkMultiplicity(handler);
// 构建 AbstractChannelHandlerContext 节点
newCtx = newContext(group, filterName(name, handler), handler);
// 添加到链表尾部
addLast0(newCtx); // registered 为 false 表示 channel 尚未注册到 EventLoop 上。
// 添加一个任务到 PendingHandlerCallback 上,后续注册完毕,再调用 ChannelHandler.handlerAdded
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
} // registered 为 true,则立即调用 ChannelHandler.handlerAdded
EventExecutor executor = newCtx.executor();
// inEvent 用于判断当前线程是否是 EventLoop 线程。执行 ChannelHandler 时,必须在对应的 EventLoop 线程池中执行。
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
callHandlerAdded0(newCtx);
return this;
} @Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
// 检查,若不是 Sharable,而且已经被添加到其他 pipeline,则抛出异常
checkMultiplicity(handler);
// 构建 AbstractChannelHandlerContext 节点
newCtx = newContext(group, filterName(name, handler), handler);
// 添加到链表尾部
addLast0(newCtx); // registered 为 false 表示 channel 尚未注册到 EventLoop 上。
// 添加一个任务到 PendingHandlerCallback 上,后续注册完毕,再调用 ChannelHandler.handlerAdded
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
} // registered 为 true,则立即调用 ChannelHandler.handlerAdded
EventExecutor executor = newCtx.executor();
// inEvent 用于判断当前线程是否是 EventLoop 线程。执行 ChannelHandler 时,必须在对应的 EventLoop 线程池中执行。
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}
1.5. 回调添加完成事件
添加完成后,执行回调方法如下:
private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
try {
ctx.handler().handlerAdded(ctx);
ctx.setAddComplete();
} catch (Throwable t) {
…….
}
会执行handler的handlerAdded 方法,这是一个回调方法。添加完成后的回调代码,基本上写在这里。
8. 小结
至此,牛逼的Netty Handler和Netty Reactor 介绍完了。
对于Pipeline模式和基于Pipeline的Netty 入站和出站的事件传输机制,【疯狂创客圈】在后面的系列死磕文章,会做一个非常精彩的介绍。
ChannelHandler揭秘(Netty源码死磕5)的更多相关文章
- Pipeline模式(netty源码死磕6)
精进篇:netty源码死磕6 巧夺天工--Pipeline模式揭秘 1. 巧夺天工--Pipeline模式揭秘 1.1. Pipeline模式简介 管道的发名者叫,Malcolm Douglas M ...
- JAVA NIO 简介 (netty源码死磕1.1)
[基础篇]netty 源码死磕1.1: JAVA NIO简介 1. JAVA NIO简介 Java 中 New I/O类库 是由 Java 1.4 引进的异步 IO.由于之前老的I/O类库是阻塞I/ ...
- Java NIO Buffer(netty源码死磕1.2)
[基础篇]netty源码死磕1.2: NIO Buffer 1. Java NIO Buffer Buffer是一个抽象类,位于java.nio包中,主要用作缓冲区.Buffer缓冲区本质上是一块可 ...
- EventLoop(netty源码死磕4)
精进篇:netty源码 死磕4-EventLoop的鬼斧神工 目录 1. EventLoop的鬼斧神工 2. 初识 EventLoop 3. Reactor模式回顾 3.1. Reactor模式的组 ...
- Netty源码死磕一(netty线程模型及EventLoop机制)
引言 好久没有写博客了,近期准备把Netty源码啃一遍.在这之前本想直接看源码,但是看到后面发现其实效率不高, 有些概念还是有必要回头再细啃的,特别是其线程模型以及EventLoop的概念. 当然在开 ...
- Netty环境搭建 (源码死磕2)
[正文]netty源码 死磕2: 环境搭建 本小节目录 1. Netty为什么火得屌炸天? 1.1. Netty是什么? 1.2. Netty火到什么程度呢? 1.3. Netty为什么这么火? 2 ...
- Netty 源码(ChannelHandler 死磕)
精进篇:netty源码死磕5 - 揭开 ChannelHandler 的神秘面纱 目录 1. 前言 2. Handler在经典Reactor中的角色 3. Handler在Netty中的坐标位置 4 ...
- Pipeline(netty源码)
精进篇:netty源码死磕6 巧夺天工--Pipeline模式揭秘 1. 巧夺天工--Pipeline模式揭秘 1.1. Pipeline模式简介 管道的发名者叫,Malcolm Douglas M ...
- Future Promise 模式(netty源码9)
netty源码死磕9 Future Promise 模式详解 1. Future/Promise 模式 1.1. ChannelFuture的由来 由于Netty中的Handler 处理都是异步IO ...
随机推荐
- 第5章-unix网络编程 TCP/服务端程序示例
这一章主要是完成一个完整的tcp客户/服务器程序.通过一很简单的例子.弄清客户和服务器如何启动,如何终止,发生了某些错误会发生什么.这些事很重要的 客户端代码 #include "unp. ...
- 在Ubuntu / Ubuntu Kylin下安装和卸载 Nodepadqq
在Ubuntu / Ubuntu Kylin下安装和卸载 Nodepadqq 对于Ubuntu发行版本可以通过PPA安装,命令如下: sudo add-apt-repository p ...
- py2exe使用方法 (含一些调试技巧,如压缩email 类)
http://justcoding.iteye.com/blog/900993 一.简介 py2exe是一个将python脚本转换成windows上的可独立执行的可执行程序(*.exe)的工具,这样, ...
- [react-router] 平时积累
path通配符: <Route path="/hello/:name"> // 匹配 /hello/michael // 匹配 /hello/ryan <Rout ...
- (49)C# npoi-word
//新建段落 XWPFParagraph p1 = doc.CreateParagraph(); //对齐方式 p1.SetAlignment(ParagraphAlignment.LEFT); p1 ...
- Java爬虫系列三:使用Jsoup解析HTML
在上一篇随笔<Java爬虫系列二:使用HttpClient抓取页面HTML>中介绍了怎么使用HttpClient进行爬虫的第一步--抓取页面html,今天接着来看下爬虫的第二步--解析抓取 ...
- JavaEETest
原文:https://github.com/lenve/JavaEETest
- 拦截器及 Spring MVC 整合
一.实验介绍 1.1 实验内容 本节课程主要利用 Spring MVC 框架实现拦截器以及 Spring MVC 框架的整合. 1.2 实验知识点 Spring MVC 框架 拦截器 1.3 实验环境 ...
- 转: svn服务器路径名修改(不需要全部重新拉取文件)
svn路径名修改之后, 一大波的研发代码都可能面临变更.还有有一个svn relote神器 大家可以借助各自的SVN工具中哦relote命令完成路径的切换,而不需要全部重新download所有的新路径 ...
- json解析神器 jsonpath的使用
转载:http://blog.csdn.net/qq_20641565/article/details/77162868 如果项目需求是从某些复杂的json里面取值进行计算,用jsonpath+IK( ...