从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