源码解析-Netty源码之Channel的rigister过程

 从Netty官方给出的example包着手点分析,echo 回声,也就是客户端传什么,服务端传回什么

 

先从客户端开始看

 

属性,ip地址,端口号,数据大小之类的 四个写死了的

 判断ssl是否为空,来决定是否需要初始化SslContext (可以理解为一些客户端的初始化配置,我们的例子中默认是null)

下面重点来了,前面暂时都可以忽略

核心点几步:

1. 创建一个线程池组group;  //初始化线程池系列 包括group loop的初始化,配置了chooser,以及引用EventExecutor代表loop

2. 创建一个客户端Bootstrap;

3. 加入线程池组,

4. 加入socketChannel,     //创建channelFactory 将来用来创建NioSocketChannel

5. 定义了ChannelInitializer,重写initChannel()方法,调用Channel的pipeline,加入了head,tail和自定义的handel;

6. 异步connect;       //这一步做的事比较多,new一个channel,初始化一个pipeline,将ChannelInitializer内部的handler全部加入到新的pipeline。从group中拿一个loop 将channel注入到loop中,启动loop线程,将channel注册到selector上

7. 关闭channel;

8. 关闭线程池组group;

 

这一篇重点分析第6步  客户端来说 异步connect过程

回顾一下前两篇,

捋顺了,现在Bootstrp初始化成这个样子了还记得吗

channel还没有真正实例化,也没和group,pipeline绑定,还有一些细节要处理,这一篇就要搞清楚这些事。 重点敲黑板*3

---

 

 

1. 从客户端bootstrap.connect()出发,先且不关注.sync()

传进来ip和端口号,直接封装成了jdk的 InetSocketAddress对象 继续往下走

 这里有个判断,判断一下bootstrap的handler是不是null,也就是之前搞的ChannelInitializer是不是Null

为什么是config.handler()呢?  这个config是bootstrap的一个全部变量,

so config.handler() 其实就是bootstrap.handler(),至于为什么这么搞呢,个人认为可能是为了责任分离吧,让bootstrap安安心心做客户端的事,配置的这些事都交给bootstrapConfig来代理

回到正题 继续connect()

initAndResgiter()  //初始化并注册 ,这步非常重要,完成了channel的初始化工作,并且将group与channel绑定。 这是个异步方法,后面会详细介绍异步,也就是说他可能会执行半天还没执行完,但是主程序并没空等他执行完 继续往下走

获取刚刚初始化的Channel

if( 初始化异步执行完了){

  if( 初始化失败了){

    直接return;

  }

  channel连接 传入地址及端口号等

}else {

  如果异步没执行完,加个监听继续执行。如果抛异常则创建失败,成功则执行channel连接

}

最后结果返回Promise

 

这里有个概念 promise是啥呢?  

promise可以理解为 channel和loop的结合体吧

也就是说 整个doResolveAndConnect() 这一步就是 将channel和loop绑定成一个promise  channel完成socket连接 然后将promise返回

下面看一下细节

 

 

2. initAndRegister()  初始化并注册, 分为两部分说吧 这是上半部分

用channelFactory创建一个Channel实例,还记得怎么创建的吗,通过构造方法反射实现的不是吗 淦

init(channel) 

初始化一个新的pipeline  (这个pipeline就是channel实例化时候内部创建的pipeline)

把bootstrap的handler加入到pipeline (这个handler不就是 从前那个ChannelInitializer吗 淦)

在设置一下其他配置intit()就算完事了

initAndRegister上半部分 再处理一下初始化异常的情况 就没啥了

 

initAndRegister的下半部分

最核心的一个地方 异步调用 config().group().register(channel);

顾名思义,将channel注册给group  ,其实做的事情就是从group中取出一个loop 绑定给channel,还会为channel注册一个selector用来监听事件

也是异步执行的,如果中间抛异常了 把channel关闭

最后 return future;

 

整一小段快乐翻译 

如果我们到了这里promise没有失败,会有以下情况的一种:

1. 如果我们尝试从当前eventloop的线程注册,这个注册在这个时候完成了。现在做bind()或者connect()就是安全的

2. 如果我们尝试从其他线程注册,注册请求已经成功地被加入到loop的任务队列中等待执行。 

这时候尝试bind()或者connect()也是安全的,因为 这两个操作将会在注册任务开始执行才会执行,因为register(),bind(),connect()全都绑定在了同一个线程上 over

 

其实这段注释就是想告诉我们 只要promise没毛病 不管loop的线程是不是当前线程 接下来的绑定连接都会成功的,只是执行时间的问题

那这说的都是啥意思呢,接着往下看看register()方法呗

 

 

3.  config().group().register(channel)  这个方法是谁的呢,

config就是 bootstrapConfig ,就算是bootstrap的代理。那group就是bootstrap里存的group了,那就是把channel注册到group呗

先看这里调用了next().resigter()  next()是啥呢

对呗,之前线程池中说过的 chooser 负责从group中挑选出一个打工人loop嘛,那么这个register()也就是挑选出来的loop执行的了

再回到register()

 没啥说的,把channel封装成一个promise然后继续往下传,也就在这了,channel和loop正是结合成了promise

这里调用的channel内部的unsafe来执行的register(), 往下走。 敲黑板,接下来算是重点了

我上来就是几个判断,

先判断 loop是不是null啊

再判断注册过了没啊,如果注册过了还玩个锤子

最后判断一下这个loop是否矛盾  

完成几个判断,将loop设置到channel内部

if (loop的线程是当前线程){

  直接register0();

}else{ 

  try{

    启动loop 来执行register0();  看到没 看到没 ! ! ! loop创建出来这么久 一直都没有正式启动过 到这才真正的执行

  }

}

上面说明了什么呢  如果loop是当前线程,说明这个loop已经不是第一次了,早就被执行过了(因为一个打工人loop可能同时给好几个channel干活啊 ...)

进入到else分支,说明这个打工人刚入职场,才会第一次启动

 

 

4.仔细地追一下这个打工人的execute()  

判断一下任务非空 继续 execute(任务,是否立即执行)  

boolean inEventLoop =  loop的线程是否是当前线程;

把任务入队;

if (如果不是当前线程){   //第一次肯定不是啊

  stratThread(); 开启线程

  if( 如果线程池关闭了){

    移除任务,拒绝策略走起

  }

}

熟悉的任务阻塞队列,熟悉的关闭线程池 拒绝策略,还是JUC熟悉的味道

 

5. startThread()

 其他忽略,doStratThread()

这个executor是啥呢  第一篇讲的ThreadPerTaskExecutor 还记不记得了

那 现在executor终于做这个事了,将当前线程给loop当作私有线程,然后启动loop的run()方法,开始做任务

执行SingleThreadEventExecutor.run()方法,这个方法就不看源码了,很长很长, 大体就是相当于JUC线程池里的worker,不断地从任务队列接任务做任务。

那么做的第一个任务是什么呢? 没错 就是刚才说的register0()

 

6. register0()

doRegister();  //通过jdk操作,将selector注册到channel上

pipeline.invokeHandlerAddedIfNeeded();    //这个pipeline在Channel内部新建的 目前只有head和tail。这个方法一直追下去会追到ChannelInitializer 的initChannel()方法,也就是之前在bootstrap写的,会把自建的handler加入pipeline呗

到这里就完成了把handler全部加到了pipeline  ChannelInitializer也就没用了

pipeline.fireChannelRegistered();  //点火 开炮 ,它的作用就像是个导火索,给pipeline点上火,然后从头开始往后依次执行register操作

 

 

7. pipeline.fireChannelRegistered()  点火

 

把pipeine的头节点点着了呗

 

传入头节点,调用channel的loop执行invokeChannelRegistered()

判断一下之前的handler是否完全加入了

try{

  ChannelRegistered()头节点;

 

头节点点火

找到下一个节点 继续点火

 

依次往下寻找所有 Inbound handler,执行其 channelRegistered(ctx) 操作。

到这里register 操作算是真正完成了。

 

第一篇 源码解析-Netty源码之EventLoopGroup线程池分析

第二篇    源码解析-Netty源码之Bootstrap创建,初始化Channel,Pipeline,handler

 
posted @ 2020-12-30 16:19  六小扛把子  阅读(425)  评论(0编辑  收藏  举报