原创申明:本文由公众号【猿灯塔】原创,转载请说明出处标注

今天是猿灯塔“365篇原创计划”第八篇。

接下来的时间灯塔君持续更新Netty系列一共九篇
Netty 源码解析(一): 开始
Netty 源码解析(二): Netty 的 Channel
Netty 源码解析(三): Netty 的 Future 和 Promise
Netty 源码解析(四): Netty 的 ChannelPipeline
Netty 源码解析(五): Netty 的线程池分析
Netty 源码解析(六): Channel 的 register 操作
Netty 源码解析(七): NioEventLoop 工作流程
当前:Netty 源码解析(八): 回到 Channel 的 register 操作
Netty 源码解析(九): connect 过程和 bind 过程分析
今天呢!灯塔君跟大家讲:

回到Channel 的 register 操作

我们回到前面的 register0(promise) 方法,我们知道,这个 register 任务进入到了 NioEventLoop 的 taskQueue 中,然后会启动 NioEventLoop 中的线程,该线程会轮询这个 taskQueue,然后执行这个 register 任务。
注意,此时执行该方法的是 eventLoop 中的线程:
// AbstractChannel
private void register0(ChannelPromise promise) {
try {
...
boolean firstRegistration = neverRegistered;
// *** 进行 JDK 底层的操作:Channel 注册到 Selector 上 ***
doRegister(); neverRegistered = false;
registered = true;
// 到这里,就算是 registered 了 // 这一步也很关键,因为这涉及到了 ChannelInitializer 的 init(channel)
// 我们之前说过,init 方法会将 ChannelInitializer 内部添加的 handlers 添加到 pipeline 中
pipeline.invokeHandlerAddedIfNeeded(); // 设置当前 promise 的状态为 success
// 因为当前 register 方法是在 eventLoop 中的线程中执行的,需要通知提交 register 操作的线程
safeSetSuccess(promise); // 当前的 register 操作已经成功,该事件应该被 pipeline 上
// 所有关心 register 事件的 handler 感知到,往 pipeline 中扔一个事件
pipeline.fireChannelRegistered(); // 这里 active 指的是 channel 已经打开
if (isActive()) {
// 如果该 channel 是第一次执行 register,那么 fire ChannelActive 事件
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
// 该 channel 之前已经 register 过了,
// 这里让该 channel 立马去监听通道中的 OP_READ 事件
beginRead();
}
}
} catch (Throwable t) {
...
}
}
我们先说掉上面的 doRegister() 方法,然后再说 pipeline。
 @Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
// 附 JDK 中 Channel 的 register 方法:
// public final SelectionKey register(Selector sel, int ops, Object att) {...}
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
...
}
}
}
我们可以看到,这里做了 JDK 底层的 register 操作,将 SocketChannel(或 ServerSocketChannel) 注册到 Selector 中,并且可以看到,这里的监听集合设置为了 0,也就是什么都不监听。
当然,也就意味着,后续一定有某个地方会需要修改这个 selectionKey 的监听集合,不然啥都干不了
我们重点来说说 pipeline 操作,我们之前在介绍 NioSocketChannel 的 pipeline 的时候介绍到,我们的 pipeline 现在长这个样子:
现在,我们将看到这里会把 LoggingHandler 和 EchoClientHandler 添加到 pipeline。
我们继续看代码,register 成功以后,执行了以下操作:
1pipeline.invokeHandlerAddedIfNeeded();
大家可以跟踪一下,这一步会执行到 pipeline 中 ChannelInitializer 实例的 handlerAdded 方法,在这里会执行它的 init(context) 方法:
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isRegistered()) {
initChannel(ctx);
}
}
然后我们看下 initChannel(ctx),这里终于来了我们之前介绍过的 init(channel) 方法:
 private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { // Guard against re-entrance.
try {
// 1. 将把我们自定义的 handlers 添加到 pipeline 中
initChannel((C) ctx.channel());
} catch (Throwable cause) {
...
} finally {
// 2. 将 ChannelInitializer 实例从 pipeline 中删除
remove(ctx);
}
return true;
}
return false;
}
我们前面也说过,ChannelInitializer 的 init(channel) 被执行以后,那么其内部添加的 handlers 会进入到 pipeline 中,然后上面的 finally 块中将 ChannelInitializer 的实例从 pipeline 中删除,那么此时 pipeline 就算建立起来了,如下图:
 
其实这里还有个问题,如果我们在 ChannelInitializer 中添加的是一个 ChannelInitializer 实例呢?大家可以考虑下这个情况。
pipeline 建立了以后,然后我们继续往下走,会执行到这一句:
pipeline.fireChannelRegistered();
我们只要摸清楚了 fireChannelRegistered() 方法,以后碰到其他像 fireChannelActive()、fireXxx() 等就知道怎么回事了,它们都是类似的。我们来看看这句代码会发生什么:
// DefaultChannelPipeline
1@Override
2public final ChannelPipeline fireChannelRegistered() {
3 // 注意这里的传参是 head
4 AbstractChannelHandlerContext.invokeChannelRegistered(head);
5 return this;
6}
也就是说,我们往 pipeline 中扔了一个 channelRegistered 事件,这里的 register 属于 Inbound 事件,pipeline 接下来要做的就是执行 pipeline 中的 Inbound 类型的 handlers 中的 channelRegistered() 方法。
从上面的代码,我们可以看出,往 pipeline 中扔出 channelRegistered 事件以后,第一个处理的 handler 是 head。
接下来,我们还是跟着代码走,此时我们来到了 pipeline 的第一个节点 head 的处理中:
// AbstractChannelHandlerContext
 // next 此时是 head
2static void invokeChannelRegistered(final AbstractChannelHandlerContext next) { EventExecutor executor = next.executor();
// 执行 head 的 invokeChannelRegistered()
if (executor.inEventLoop()) {
next.invokeChannelRegistered();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRegistered();
}
});
}
}
也就是说,这里会先执行 head.invokeChannelRegistered() 方法,而且是放到 NioEventLoop 中的 taskQueue 中执行的:
// AbstractChannelHandlerContext3
 private void invokeChannelRegistered() {
if (invokeHandler()) {
try {
// handler() 方法此时会返回 head
((ChannelInboundHandler) handler()).channelRegistered(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRegistered();
}
}
我们去看 head 的 channelRegistered 方法:
// HeadContext
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
// 1. 这一步是 head 对于 channelRegistered 事件的处理。没有我们要关心的
invokeHandlerAddedIfNeeded();
// 2. 向后传播 Inbound 事件
ctx.fireChannelRegistered();
}
然后 head 会执行 fireChannelRegister() 方法:
// AbstractChannelHandlerContext
1@Override
2public ChannelHandlerContext fireChannelRegistered() {
3 // 这里很关键
4 // findContextInbound() 方法会沿着 pipeline 找到下一个 Inbound 类型的 handler
5 invokeChannelRegistered(findContextInbound());
6 return this;
7}
注意:pipeline.fireChannelRegistered() 是将 channelRegistered 事件抛到 pipeline 中,pipeline 中的 handlers 准备处理该事件。而 context.fireChannelRegistered() 是一个 handler 处理完了以后,向后传播给下一个 handler。 它们两个的方法名字是一样的,但是来自于不同的类。
findContextInbound() 将找到下一个 Inbound 类型的 handler,然后又是重复上面的几个方法。
我觉得上面这块代码没必要太纠结,总之就是从 head 中开始,依次往下寻找所有 Inbound handler,执行其 channelRegistered(ctx) 操作。
说了这么多,我们的 register 操作算是真正完成了。
下面,我们回到 initAndRegister 这个方法:
 final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
} catch (Throwable t) {
...
}
// 我们上面说完了这行
ChannelFuture regFuture = config().group().register(channel);
// 如果在 register 的过程中,发生了错误
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
// 源码中说得很清楚,如果到这里,说明后续可以进行 connect() 或 bind() 了,因为两种情况:
// 1. 如果 register 动作是在 eventLoop 中发起的,那么到这里的时候,register 一定已经完成
// 2. 如果 register 任务已经提交到 eventLoop 中,也就是进到了 eventLoop 中的 taskQueue 中,
// 由于后续的 connect 或 bind 也会进入到同一个 eventLoop 的 queue 中,所以一定是会先 register 成功,才会执行 connect 或 bind
return regFuture;
}
我们要知道,不管是服务端的 NioServerSocketChannel 还是客户端的 NioSocketChannel,在 bind 或 connect 时,都会先进入 initAndRegister 这个方法,所以我们上面说的那些,对于两者都是通用的。
大家要记住,register 操作是非常重要的,要知道这一步大概做了哪些事情,register 操作以后,将进入到 bind 或 connect 操作中。
365天干货不断微信搜索「猿灯塔」第一时间阅读,回复【资料】【面试】【简历】有我准备的一线大厂面试资料和简历模板

Netty 源码解析(八): 回到 Channel 的 register 操作的更多相关文章

  1. Netty 源码解析(六): Channel 的 register 操作

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第六篇. 接下来的时间灯塔君持续更新Netty系列一共九篇   Netty 源码解析(一 ):开始 Netty ...

  2. Netty 源码解析(二):Netty 的 Channel

    本文首发于微信公众号[猿灯塔],转载引用请说明出处 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty源码解析(一):开始 当前:Netty 源码解析(二): Netty 的 Channel ...

  3. Netty 源码解析(三): Netty 的 Future 和 Promise

    今天是猿灯塔“365篇原创计划”第三篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel 当前:Ne ...

  4. Netty 源码解析(九): connect 过程和 bind 过程分析

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第九篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

  5. Netty 源码解析(七): NioEventLoop 工作流程

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第七篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

  6. Netty 源码解析(五): Netty 的线程池分析

    今天是猿灯塔“365篇原创计划”第五篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ...

  7. Netty 源码解析(四): Netty 的 ChannelPipeline

    今天是猿灯塔“365篇原创计划”第四篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ...

  8. Netty源码解析—客户端启动

    Netty源码解析-客户端启动 Bootstrap示例 public final class EchoClient { static final boolean SSL = System.getPro ...

  9. Netty源码解析---服务端启动

    Netty源码解析---服务端启动 一个简单的服务端代码: public class SimpleServer { public static void main(String[] args) { N ...

随机推荐

  1. java实现第二届蓝桥杯最小公倍数(c++)

    最小公倍数. 为什么1小时有60分钟,而不是100分钟呢?这是历史上的习惯导致. 但也并非纯粹的偶然:60是个优秀的数字,它的因子比较多. 事实上,它是1至6的每个数字的倍数.即1,2,3,4,5,6 ...

  2. java实现第四届蓝桥杯连号区间数

    连号区间数 题目描述 小明这些天一直在思考这样一个奇怪而有趣的问题: 在1~N的某个全排列中有多少个连号区间呢?这里所说的连号区间的定义是: 如果区间[L, R] 里的所有元素(即此排列的第L个到第R ...

  3. 使用百度地图时,Application类的onCreate执行两次的解决方案

    应用做的匆忙,很多地方只顾实现功能,没有兼顾好性能,所以停下来重构代码优化性能,结果在打log看启动时间的时候,发现Application的onCreate执行了多次,这样导致重复初始化资源,初始化了 ...

  4. Flask 安装和简单使用

    安装 pip install flask # 1 导入 Falsk from flask import Flask # 2 生成一个 Flask 对象,__name__表示当前文件的名字 app = ...

  5. Telegraf和Grafana监控多平台上的SQL Server-自定义监控数据收集

    问题 在上一篇文章中,我们使用Telegraf自带的Plugin配置好了的监控,但是自带的Plugin并不能完全覆盖我们想要的监控指标,就需要收集额外的自定义的监控数据,实现的方法有: 开发自己的Te ...

  6. Dedecms 修改当前位置样式

    当前列表页间隔符样式修改 后台-->系统基本参数-->核心设置-->栏目位置的间隔符号 只有二级位置栏目时的间隔符去除 找到include文件中的typelink.class.php ...

  7. Seafile的配置

    如果部署在apache或nginx后面,就是访问默认80或443.反向代理会帮你转到8000和8082上. 我使用443,我在路由器上设定了转发,比如 对外网12323这个端口会转到内网地址443上 ...

  8. python_lesson1 数学与随机数 (math包,random包)

    math包 math包主要处理数学相关的运算.math包定义了两个常数: math.e   # 自然常数e math.pi  # 圆周率pi   此外,math包还有各种运算函数 (下面函数的功能可以 ...

  9. 10.DRF-认证

    Django rest framework源码分析(1)----认证 一.基础 1.1.安装 两种方式: github pip直接安装 pip install djangorestframework ...

  10. JS中函数执行顺序的问题?

    作者:知乎用户链接:https://www.zhihu.com/question/23564807/answer/82996422来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注 ...