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 ...
随机推荐
- Java实现 LeetCode 2 两数相加
两数相加 给出两个 非空 的链表用来表示两个非负的整数.其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字. 如果,我们将这两个数相加起来,则会返回一个新的链表来表 ...
- java实现第五届蓝桥杯供水设施
供水设施 X星球的居民点很多.Pear决定修建一个浩大的水利工程,以解决他管辖的N个居民点的供水问题.现在一共有N个水塔,同时也有N个居民点,居民点在北侧从1号到N号自西向东排成一排:水塔在南侧也从1 ...
- Python 网络爬虫基本概念篇
爬虫的概念 网络爬虫(又称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本.这是百度百科对爬虫的定义,其实,说简单点,爬虫 ...
- Jmeter连接数据库进行参数化
实际使用Jmeter进行性能测试或接口测试自动化过程中,很多场景需要从数据库中获取一些关键性参数,或进行一些断言,比较,那么如何进行数据库连接以及怎么获取参数就变得尤为重要 一.下载mysql驱动 1 ...
- Python 图像处理 OpenCV (7):图像平滑(滤波)处理
前文传送门: 「Python 图像处理 OpenCV (1):入门」 「Python 图像处理 OpenCV (2):像素处理与 Numpy 操作以及 Matplotlib 显示图像」 「Python ...
- win10系统无法删除文件的解决方法
方法/步骤 1:首先进入不能删除的文件所在的文件夹 2:右键单击此文件夹,选择授予访问权限 3:在授权界面选择删除权限 4:在删除权限中点击更改共享权限 5:我们选择administrator级别,点 ...
- 总结:修改相关postgres用户密码
1.修改linux系统postgres用户的密码 PostgreSQL会创建一个默认的linux用户postgres,修改该用户密码的方法如下: 步骤一:删除用户postgres的密码 sudo p ...
- css如何实现水平垂直居中
方法 居中元素定宽高固定 PC兼容性 移动端兼容性 absolute + 负margin 是 ie6+, chrome4+, firefox2+ 安卓2.3+, iOS6+ absolute + ma ...
- 【Android】使用Appium+python控制真机,碰到的问题以及处理(持续更新)
问题: selenium.common.exceptions.WebDriverException: Message: A new session could not be created. (Ori ...
- Charles 安装证书后依旧抓取不到https请求的解决方案
1.打开charles——>help——>SSL proxying——>Install Charles Root Certificate 证书安装后,抓取https的包 2.查看Pr ...