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 116 填充每个节点的下一个右侧节点指针
116. 填充每个节点的下一个右侧节点指针 给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点.二叉树定义如下: struct Node { int val; Node *left ...
- java实现迷宫走法
** 迷宫走法** 迷宫问题 对于走迷宫,人们提出过很多计算机上的解法.深度优先搜索.广度优先搜索是使用最广的方法.生活中,人们更愿意使用"紧贴墙壁,靠右行走"的简单规则. 下面的 ...
- 从零搭建Window前端开发环境
前言 作为一个小前端,是否因为搭建环境烦恼过,是否因为npm等国外镜像踩坑过,不要怕,接下来跟着我一步步搭建适合自己的开发环境吧!!! node 这个不用说了吧,我们经常和他打交道,无论是 gulp. ...
- 浅谈js原型
前言 突发奇想,想写一篇原型的文章,也可能是因为对原型有更深的理解吧,在这里做个记录,来记录下自己的理解加深下记忆. 总之,希望本文的内容能够对您的学习或者工作有所帮助.另,如果有任何的错误或者不足请 ...
- Tomcat 配置必备的 10 个小技巧
现在开发Java Web应用,建立和部署Web内容是一件很简单的工作.使用Jakarta Tomcat作为Servlet和JSP容器的人已经遍及全世界.Tomcat具有免费.跨平台等诸多特性,并且更新 ...
- (三)Host头攻击
01 漏洞描述 为了方便获取网站域名,开发人员一般依赖于请求包中的Host首部字段.例如,在php里用_SERVER["HTTP_HOST"].但是这个Host字段值是不可信赖的( ...
- 你是项目经理了![IT项目经理成长晋升记1]
凉爽的秋风,吹走了严热的夏季,K公司内部传来一个重磅消息.销售团队披荆斩棘,过三关斩六将,成功胜出,拿下公司在C省的首单,为C省市场的开拓打下了基础. K公司是2003年“非典”时期成立,坐落在美丽的 ...
- iOS-Swift版本自定义CStextView的实现
CSTextView继承自UITextView,并为其增加了placeHolder属性,支持代码和SB方式创建:demo实现了CSTextView的自动排版 效果图 CSTextView地址: htt ...
- 创建sudo -i免密权限账户
项目原因,服务器需要创建普通用户,但又不能让用户拿到root密码. 创建用户 [root@bogon ~]# groupadd connect [root@bogon ~]# useradd -g c ...
- 下拉式菜单中的内容堆叠(ul型)
今天使用ul创建下拉式菜单,菜单中的内容堆在了一起. 这是我的html代码 <!DOCTYPE html> <html lang="en"> <hea ...