Netty 源码解析(八): 回到 Channel 的 register 操作
原创申明:本文由公众号【猿灯塔】原创,转载请说明出处标注
今天是猿灯塔“365篇原创计划”第八篇。
回到Channel 的 register 操作
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) {
...
}
}
@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) {
...
}
}
}
当然,也就意味着,后续一定有某个地方会需要修改这个 selectionKey 的监听集合,不然啥都干不了
现在,我们将看到这里会把 LoggingHandler 和 EchoClientHandler 添加到 pipeline。
1pipeline.invokeHandlerAddedIfNeeded();
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isRegistered()) {
initChannel(ctx);
}
}
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 中添加的是一个 ChannelInitializer 实例呢?大家可以考虑下这个情况。
pipeline.fireChannelRegistered();
1@Override
2public final ChannelPipeline fireChannelRegistered() {
3 // 注意这里的传参是 head
4 AbstractChannelHandlerContext.invokeChannelRegistered(head);
5 return this;
6}
// 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();
}
});
}
}
private void invokeChannelRegistered() {
if (invokeHandler()) {
try {
// handler() 方法此时会返回 head
((ChannelInboundHandler) handler()).channelRegistered(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRegistered();
}
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
// 1. 这一步是 head 对于 channelRegistered 事件的处理。没有我们要关心的
invokeHandlerAddedIfNeeded();
// 2. 向后传播 Inbound 事件
ctx.fireChannelRegistered();
}
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。 它们两个的方法名字是一样的,但是来自于不同的类。
我觉得上面这块代码没必要太纠结,总之就是从 head 中开始,依次往下寻找所有 Inbound handler,执行其 channelRegistered(ctx) 操作。
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;
}
365天干货不断微信搜索「猿灯塔」第一时间阅读,回复【资料】【面试】【简历】有我准备的一线大厂面试资料和简历模板
Netty 源码解析(八): 回到 Channel 的 register 操作的更多相关文章
- Netty 源码解析(六): Channel 的 register 操作
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第六篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一 ):开始 Netty ...
- Netty 源码解析(二):Netty 的 Channel
本文首发于微信公众号[猿灯塔],转载引用请说明出处 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty源码解析(一):开始 当前:Netty 源码解析(二): Netty 的 Channel ...
- Netty 源码解析(三): Netty 的 Future 和 Promise
今天是猿灯塔“365篇原创计划”第三篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel 当前:Ne ...
- Netty 源码解析(九): connect 过程和 bind 过程分析
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第九篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...
- Netty 源码解析(七): NioEventLoop 工作流程
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第七篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...
- Netty 源码解析(五): Netty 的线程池分析
今天是猿灯塔“365篇原创计划”第五篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ...
- Netty 源码解析(四): Netty 的 ChannelPipeline
今天是猿灯塔“365篇原创计划”第四篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ...
- Netty源码解析—客户端启动
Netty源码解析-客户端启动 Bootstrap示例 public final class EchoClient { static final boolean SSL = System.getPro ...
- Netty源码解析---服务端启动
Netty源码解析---服务端启动 一个简单的服务端代码: public class SimpleServer { public static void main(String[] args) { N ...
随机推荐
- Linux 系统定时任务
crond服务管理与访问控制 启动 service crond restart 开机自启动 chkconfig crond on 用户的crontab设置 crontab [选项],其中 -e :编辑 ...
- python XlsxWriter模块创建aexcel表格
https://blog.csdn.net/qq_41030861/article/details/82148777 安装使用pip install XlsxWriter来安装,Xlsxwriter用 ...
- 白嫖永久免费云服务器教程,永久免费虚拟主机、永久免费云数据库、搭建FTP服务器、服务器安装Linux / windows操作系统、服务器部署网站、宝塔一键部署多网站、独立ip、永久国内高速云服务器
一.准备工作 1. 注册账号 声明:切记不可用服务器做违法的事情 申请地址:https://www.sanfengyun.com/ 图文教程地址:https://www.cnblogs.com/zwn ...
- 微信小程序生命周期,事件
目录 双线程模型 小程序中 app.js 中的生命周期 小程序的页面的生命周期 小程序的事件 双线程模型 像 Vue 的双向数据绑定 总结: 在渲染层将wxml文件与wxss文件转成js对象,也就是虚 ...
- Nginx源码编译安装选项
[Nginx源码编译过程] make是用来编译的,它从Makefile中读取指令,然后编译. make install是用来安装的,它也从Makefile中读取指令,安装到指定的位置. configu ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(五)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- ubuntu安装mysql并使用Navicat连接
今天配置了一下自己的服务器,发现网上很多教程都有点老,而且不是很全.干脆就写一篇Ubuntu安装mysql,并用Navicat连接的全流程 一.安装mysql 1. sudo apt-get inst ...
- linux安装syncthing
https://blog.csdn.net/weixin_30527551/article/details/98882344 https://syncthing.net/downloads/ http ...
- CentOS7.5搭建Hive2.3.3
一 Hive的下载 软件下载地址:https://mirrors.tuna.tsinghua.edu.cn/apache/hive/ 这里下载的版本是:apache-hive-2.3.3-bin.t ...
- Springboot打包后,获取不到resource目录下资源文件的报错
1.问题: java.io.FileNotFoundException ****目录下找不到模板文件 在使用Springboot启动类启动没有错,但是打包放到tomcat.东方通这些外部容器上报错,在 ...