Netty 源码分析——ChannelPipeline
Netty 源码分析——ChannelPipeline
通过前面的两章我们分析了客户端和服务端的流程代码,其中在初始化 Channel 的时候一定会看到一个 ChannelPipeline。所以在 Netty 每个 Channel 中有且仅有一个 ChannelPipeline。
比如我们来看 NioSocketChannel 的构造器初始化流程是
NioSocketChannel -> AbstractNioByteChannel -> AbstractNioChannel -> AbstractChannel
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
我们发现 AbstractChannel 内部维护了一个 pipeline 属性并且在构造器中调用了 newChannelPipeline();
protected DefaultChannelPipeline newChannelPipeline() {
return new DefaultChannelPipeline(this);
}
在 newChannelPipeline(); 方法中实例化了一个 DefaultChannelPipeline 对象,传递的 this 呢就是我们初始化的 NioSocketChannel 的实例了。从代码上就验证了每个 Channel 只对应一个 DefaultChannelPipeline 实例。
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;
}
上面代码主要代码是 tail 和 head 的赋值。实例化两个 ChannelHandlerContext, 一个是 HeadContext 实例 head, 另一个是 TailContext 实例 tail 接着将 head 和 tail 互相指向, 构成一个双向链表。
final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler {
}
final class HeadContext extends AbstractChannelHandlerContext
implements ChannelOutboundHandler, ChannelInboundHandler {
}
我们可以发现 TailContext、HeadContext 都继承了 AbstractChannelHandlerContext 各自都有实现了 Out 或 In 的 Handler 接口,因此它们有 Context 和 Handler 的双重属性。
那么现在 Channel 和 ChannelPipeline 的对应关系就是如下图
ChannelInitializer 的添加
Channel 和 ChannelPipeline 的关系我们已经了解了,那么我们定义的 handle 中明明是一个 ChannelInitializer 的实例啊,那么我们就来看一下。
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("clientHandle", new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
}
});
}
});
上面代码在调用 handle 方法时传递了 ChannelInitializer 对象,提供了一个 initChannel 函数来给我们重写。我们首先来看下 ChannelInitializer 类结构。
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {
}
public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {
}
public abstract class ChannelHandlerAdapter implements ChannelHandler {
}
从上面代码我们可以看到 ChannelInitializer 实现了 ChannelHandler。
那么我们往回看代码看回到 io.netty.bootstrap.Bootstrap#init 中。
void init(Channel channel) throws Exception {
ChannelPipeline p = channel.pipeline();
p.addLast(config.handler());
// 下面代码省略
}
init 方法中调用了 channel 属性中 pipeline() 来获取到 pipeline 并且调用 pipeline 的 addLast 函数将 handler 传进去,其实穿进去的就是 ChannelInitializer 实例。
@Override
public final ChannelPipeline addLast(ChannelHandler... handlers) {
// 内部继续调用 addLast 第一个参数为 null 第二个参数 ChannelInitializer 实例
return addLast(null, handlers);
}
@Override
public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
if (handlers == null) {
throw new NullPointerException("handlers");
}
// 取出我们的 ChannelInitializer 实例内部继续调用 addLast 并且前两个参数都是 null
for (ChannelHandler h: handlers) {
if (h == null) {
break;
}
addLast(executor, null, h);
}
return this;
}
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
// 检查 handler 是否重复添加
checkMultiplicity(handler);
// 创建一个 Context 实例,第一个参数是 null 上面传的,第二个是获取 name 规则是实例的 simpleClassName + #0, 第三个是 ChannelInitializer 实例
newCtx = newContext(group, filterName(name, handler), handler);
// 重点添加到末尾
addLast0(newCtx);
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
callHandlerAddedInEventLoop(newCtx, executor);
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}
上面的 addLast 方法中,很多重载的方法, 我们关注最后这个比较重要的方法就可以了。上面代码我们可以看到 newContext 这个函数。
private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
}
newContext 函数是返回了一个 DefaultChannelHandlerContext 的实例 this 就是 ChannelPipeline, group 是 null,name 是生成的 name,handler 就是 ChannelInitializer 实例。
我们来进入 DefaultChannelHandlerContext 构造器器看看发生了什么。
AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor,String name, Class<? extends ChannelHandler> handlerClass) {
this.name = ObjectUtil.checkNotNull(name, "name");
this.pipeline = pipeline;
this.executor = executor;
this.executionMask = mask(handlerClass);
ordered = executor == null || executor instanceof OrderedEventExecutor;
}
上面代码都是一些属性赋值,但是我们注意 this.executionMask = mask(handlerClass); 这一行我们继续看
static int mask(Class<? extends ChannelHandler> clazz) {
Map<Class<? extends ChannelHandler>, Integer> cache = MASKS.get();
Integer mask = cache.get(clazz);
if (mask == null) {
mask = mask0(clazz);
cache.put(clazz, mask);
}
return mask;
}
先是从缓存获取第一次肯定为 null,为 null 后的调用了 mask0(clazz); 获取出 int 然后再放入缓存中,那么重点就是在 mask0(clazz); 中了,里面代码很多省略了不少保留了主要的几行。
private static int mask0(Class<? extends ChannelHandler> handlerType) {
int mask = MASK_EXCEPTION_CAUGHT;
try {
if (ChannelInboundHandler.class.isAssignableFrom(handlerType)) {
mask |= MASK_ALL_INBOUND;
}
if (ChannelOutboundHandler.class.isAssignableFrom(handlerType)) {
mask |= MASK_ALL_OUTBOUND;
}
} catch (Exception e) {
}
return mask;
}
上面代码我们发现会先去判断传入的 class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,那么指定的 class 参数就是 ChannelInboundHandler 了。因为 ChannelInitializer 仅仅实现了 ChannelInboundHandler 所以会执行 mask |= MASK_ALL_INBOUND; 将他标识为是一个 MASK_ALL_INBOUND 返回。而这个 int 值会传递到父类 AbstractChannelHandlerContext 中,并初始化 this.executionMask 字段。
创建好 DefaultChannelHandlerContext 后我们回来 addLast(); 函数中,会内部继续调用 addLast0(); 方法。
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}
上面代码是将创建好的 newCtx 前置引用指向 head 后置引用指向 tail,head 后置引用指向 newCtx,tail 前置引用指向 newCtx。显然, 这个代码就是典型的双向链表的插入操作了. 当调用了 addLast 方法后, Netty 就会将此 handler 添加到双向链表中 tail 元素之前的位置.
我们知道了 ChannelInitializer 是什么时候添加进去的了,那么我们在 ChannelInitializer 自定义的 handle 是什么时候添加的呢?
自定义 handle 添加
我们来回顾一下客户端在 channel 注册的过程,最后我们发现真正执行的是在 NioSocketChannel$NioSocketChannelUnsafe.register() 中完成的。仔细看里面的代码我们会发现在注册完之后有一行 pipeline.fireChannelRegistered();
@Override
public final ChannelPipeline fireChannelRegistered() {
AbstractChannelHandlerContext.invokeChannelRegistered(head);
return this;
}
static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
EventExecutor executor = next.executor();
// 判断当前线程是不是一个 EventLoop 如果不是就异步执行
if (executor.inEventLoop()) {
next.invokeChannelRegistered();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRegistered();
}
});
}
}
上面代码执行了 invokeChannelRegistered. 我们继续看看
private void invokeChannelRegistered() {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRegistered(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRegistered();
}
}
上面代码主要做了判断当前的 handler 状态是否正确,正确执行 channelRegistered(),不正确执行 fireChannelRegistered().我们先来看看 fireChannelRegistered();
@Override
public ChannelHandlerContext fireChannelRegistered() {
invokeChannelRegistered(findContextInbound(MASK_CHANNEL_REGISTERED));
return this;
}
看到上面代码 findContextInbound() 函数传入了一个 MASK_CHANNEL_REGISTERED 那么这个 MASK_CHANNEL_REGISTERED 是什么呢?
static final int MASK_CHANNEL_REGISTERED = 1 << 1;
private AbstractChannelHandlerContext findContextInbound(int mask) {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.next;
} while ((ctx.executionMask & mask) == 0);
return ctx;
}
我们发现 findContextInbound() 函数中是找到一个 executionMask 为 MASK_CHANNEL_REGISTERED 的 context.那么 MASK_CHANNEL_REGISTERED 到底是什么?
private static final int MASK_ALL_INBOUND = MASK_EXCEPTION_CAUGHT | MASK_CHANNEL_REGISTERED |
MASK_CHANNEL_UNREGISTERED | MASK_CHANNEL_ACTIVE | MASK_CHANNEL_INACTIVE | MASK_CHANNEL_READ |
MASK_CHANNEL_READ_COMPLETE | MASK_USER_EVENT_TRIGGERED | MASK_CHANNEL_WRITABILITY_CHANGED;
还记得 MASK_ALL_INBOUND 吗,我们创建 new DefaultChannelHandlerContext(this, childExecutor(group), name, handler); 的时候不就是设置为 MASK_ALL_INBOUND 吗?看 MASK_ALL_INBOUND 第二个参数,不就是传递到 findContextInbound() 的值吗?由此我们可以得出结论 findContextInbound() 就是要找到一个 InboundHandler 出来传入 invokeChannelRegistered() 中然后执行 channelRegistered() 函数。我们继续跟进。发现 io.netty.channel.ChannelInitializer#channelRegistered 这个方法不就是 ChannelInitializer 里的吗。
public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
if (initChannel(ctx)) {
ctx.pipeline().fireChannelRegistered();
removeState(ctx);
} else {
ctx.fireChannelRegistered();
}
}
我们先关注 initChannel(); 方法将自己传递了进去。
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
if (initMap.add(ctx)) {
try {
// 执行重写的 initChannel
initChannel((C) ctx.channel());
} catch (Throwable cause) {
} finally {
ChannelPipeline pipeline = ctx.pipeline();
// 将自己重链表中删除
if (pipeline.context(this) != null) {
pipeline.remove(this);
}
}
return true;
}
return false;
}
看到上面代码我们就一目了然了调用我们重写的 initChannel 方法将 NioSocketChannel 传递进去。
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("stringDecoder", new StringDecoder());
pipeline.addLast("stringEncoder", new StringEncoder());
pipeline.addLast("clientHandle", new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
}
});
}
});
上面代码就正式的将我们自定义的一些 handle 给放入了 Channel 对应的 ChannelPipeline 中。之后在 finally 中将自己从链表中给删除。
到了这里, 我们的 自定义 ChannelHandler 的添加过程 也分析的查不多了.本章结束,谢谢观看!
Netty 源码分析——ChannelPipeline的更多相关文章
- netty源码分析之二:accept请求
我在前面说过了server的启动,差不多可以看到netty nio主要的东西包括了:nioEventLoop,nioMessageUnsafe,channelPipeline,channelHandl ...
- Netty源码分析第1章(Netty启动流程)---->第3节: 服务端channel初始化
Netty源码分析第一章:Netty启动流程 第三节:服务端channel初始化 回顾上一小节的initAndRegister()方法: final ChannelFuture initAndRe ...
- Netty源码分析第3章(客户端接入流程)---->第2节: 处理接入事件之handle的创建
Netty源码分析第三章: 客户端接入流程 第二节: 处理接入事件之handle的创建 上一小节我们剖析完成了与channel绑定的ChannelConfig初始化相关的流程, 这一小节继续剖析客户端 ...
- Netty源码分析第3章(客户端接入流程)---->第3节: NioSocketChannel的创建
Netty源码分析第三章: 客户端接入流程 第三节: NioSocketChannel的创建 回到上一小节的read()方法: public void read() { //必须是NioEventLo ...
- Netty源码分析第3章(客户端接入流程)---->第4节: NioSocketChannel注册到selector
Netty源码分析第三章: 客户端接入流程 第四节: NioSocketChannel注册到selector 我们回到最初的NioMessageUnsafe的read()方法: public void ...
- Netty源码分析第4章(pipeline)---->第2节: handler的添加
Netty源码分析第四章: pipeline 第二节: Handler的添加 添加handler, 我们以用户代码为例进行剖析: .childHandler(new ChannelInitialize ...
- Netty源码分析第4章(pipeline)---->第3节: handler的删除
Netty源码分析第四章: pipeline 第三节: handler的删除 上一小节我们学习了添加handler的逻辑操作, 这一小节我们学习删除handler的相关逻辑 如果用户在业务逻辑中进行c ...
- Netty源码分析第4章(pipeline)---->第4节: 传播inbound事件
Netty源码分析第四章: pipeline 第四节: 传播inbound事件 有关于inbound事件, 在概述中做过简单的介绍, 就是以自己为基准, 流向自己的事件, 比如最常见的channelR ...
- Netty源码分析第4章(pipeline)---->第6节: 传播异常事件
Netty源码分析第四章: pipeline 第6节: 传播异常事件 讲完了inbound事件和outbound事件的传输流程, 这一小节剖析异常事件的传输流程 首先我们看一个最最简单的异常处理的场景 ...
随机推荐
- 5. 使用grafana模板
我们刚刚配置的Dashboard还是有点简陋,为了界面美观,这里我们这里使用一些模板 1.使用node_exporter模板 去grafana官网下载模板,点击Download,如下所示 2.导入模板 ...
- Django框架(二十三)—— Django rest_framework-解析器
解析器 一.解析器的作用 根据请求头 content-type 选择对应的解析器对请求体内容进行处理,将传过来的数据解析成字典 二.使用解析器 1.局部使用 在视图类中重定义parser_classe ...
- jmeter beanshell postprocessor 使用
String newtoken=bsh.args[0];print(newtoken);${__setProperty(newtoken,${token},)}; String newcompanyI ...
- Spring Cloud Eureka自我保护机制
自我保护背景 首先对Eureka注册中心需要了解的是Eureka各个节点都是平等的,没有ZK中角色的概念, 即使N-1个节点挂掉也不会影响其他节点的正常运行. 默认情况下,如果Eureka Serve ...
- SpringCloud-技术专区-Zuul-使用指南
Zuul作为微服务系统的网关组件,用于构建边界服务,致力于动态路由.过滤.监控.弹性伸缩和安全. Zuul功能 认证 压力测试 金丝雀测试 动态路由 负载削减 安全 静态响应处理 主动/主动交换管理 ...
- React-Native 指定模拟器RUN-IOS
react-native run-ios --simulator "iPhone 7”
- js函数方法
/** * call()和apply * 这两个方法都是函数对象的方法,需要通过函数对象来调用 * 当对函数调用call()和apply()都会调用函数执行 * 在调用call()和apply()可以 ...
- window 下总是object_detection/protos/*.proto: No such file or directory
这是因为目前的protoc3.5有Bug,换成3.4就好了https://github.com/google/protobuf/releases/tag/v3.4.0
- 2018-2-13-win10-uwp-魔力鬼畜
title author date CreateTime categories win10 uwp 魔力鬼畜 lindexi 2018-2-13 17:23:3 +0800 2018-2-13 17: ...
- WPF 从服务器下载文件
1.先获取服务器下载地址,给出要下载到的目标地址 public void DownloadFileFromServer() { string serverFilePath = "http:/ ...