从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;
这一篇重点分析第一步 创建线程池组group 与线程loop
注意 这里的group与loop的概念区别于jdk的线程池与线程,更像是一种公司与打工人的关系,公司负责提供打工人,打工人负责具体做事情。
loop打工人用来处理连接的生命周期中所发生的事件,在内部,将会为每个Channel分配一个loop。
group 是一个loop 池,包含很多的loop,需要接活的时候直接让公司派出一个打工人来干活
Netty 为每个 Channel 分配了一个 loop,用于处理用户连接请求、对用户请求的处理等所有事件。
一个 Channel 一旦与一个 loop 相绑定,那么在 Channel 的整个生命周期内是不能改变的。一个 loop 可以与多个 Channel 绑定。即 Channel 与 EventLoop 的关系是 n:1的关系
---
继承结构
前面所说的loop和group 落地的实现就是NioEventLoop和NioEventLoopGroup
从继承关系可以看出,无论是loop还是group都继承自 jdk的Executor,都具有最原始的execute()方法
最顶层Executor,然后是线程池,然后时可以定时处理的线程池
loop继承了SingleThreadEventLoop,看名字 这是个单线程的线程池,也就是说 loop内部其实放着一个只有一个线程的线程池,具体干活就靠着一个线程了
group继承子MultithreadEventLoopGroup.多种线程事件的线程池,与其说是线程池,不如说这是一个loop池,存放了各种各样的loop,还具有一些找哪个loop出来干活的方法
源码分析
从这里为入口开始探索
先给个线程数为0,继续
先给个executor为null,继续
先给个默认的selectorProvider,用来创建selector监听端口号,这个provider方法由jdk1.4提供
给个默认的选择策略
给个默认的拒绝策略 (直接抛异常)
到了父类的构造器了,线程数如果是0,直接给个默认大小 (cpu核心数*2) 这里的args参数就是什么选择策略啊 拒绝策略啊 不是本篇重点,继续往下走
初始化了个DefaultEventExecutorChooserFactory,这东西就是用来选择loop的,也就是有需求的时候 他会从从group里选出一个loop 交给channel
1.MuiltithreadEventExecutorGroup() 构造方法 完成了所有打工人loop的初始化过程
来到重点了,重要的初始化过程都在这进行 这一个方法被我拆成了几个部分分别介绍
首先判断一下线程数,也是loop数,刚才给了 cpu核心数*2
判断executor是否为null,如果为null创建一个,刚才一直没给,会在这里new一个
executor的作用就是来一个任务 就创建一个线程。 这个executor是给loop用的,其实也可以理解成他就是loop内部的线程池,后面我们会说到 这个线程池只有一个线程
children 是什么呢,一个EventExecutor数组,在这new了一个线程数大小的数组,猜一下 这个EventExecutor是loop的一种形式,或者说他本身就是loop,存在于group中的数组
for(线程数次循环){ //循环初始化每一个child,也就是每一个loop
try{
newChild(executor,args); //创建一个child 这里声明一下说法关系: NioEventLoopGroup == group == parent == 线程池 == 公司 NioEventLoop == loop == child == 线程 == 打工人
}catch{}
finally{
如果初始化每一个child的过程中有一个失败了,就将所有的child关闭;
}
}
用传过来的chooserFactory创建一个chooser,将children传进去,chooser的next()方法 用来选择loop去干活的
初始化FutureListener,将这个listener注册给每一个loop,用来监控loop活干的怎么样。打工人心累
设置readonlyChildren集合,目前是所有loop。
2. new child(executor,args) 创建一个打工人loop的过程
NioEventLoop中落地的
创建一个taskQueue任务队列,要做的任务都会存在这 供打工人做任务。这里直接从args参数里提取的
将之前初始化的参数直接赋过来,
开启了selector
追到这里 super(parent) 这个parent也就是group,每一个loop也存了一下group的引用
其他的都是直接赋值,这里看一下executor怎么玩的
传入executor,传入 刚才的SingleThreadEventExecutor,执行传入的任务,交给下面的apply()执行
command.run()
结合之前说的,executor可以看作loop内部的一个线程池,当来了一个任务,executor直接去执行任务
3. newChooser()
两种创建chooser的策略,根据线程的数量是不是2的倍数来判断的
两种chooser的next()方法不同, 也就是挑选loop打工者的方式不同
到这里左边这一大坨线程池系列就算理清楚了