//1 netty 中使用 NioEventLoopGroup (简称 nio boss 线程)来封装线程和 selector
Selector selector = Selector.open();
//2 创建 NioServerSocketChannel,同时会初始化它关联的 handler,以及为原生 ssc 存储 config,
//netty封装的ServerSocketChannel 叫做NioServerSocketChannel
NioServerSocketChannel attachment = new NioServerSocketChannel();
//3 创建 NioServerSocketChannel 时,创建了 java 原生的 ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//为配合选择器使用,设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
//4 启动 nio boss 线程执行接下来的操作
//5 注册(仅关联 selector 和 NioServerSocketChannel),未关注事件
//将 serverSocketChannel 和 serverSocketChannel 关联起来
//attachment:将netty中的NioServerSocketChannel 作为附件与原生的ssc(serverSocketChannel)关联起来
SelectionKey selectionKey = serverSocketChannel.register(selector, 0, attachment);
//6 head -> 初始化器 -> ServerBootstrapAcceptor -> tail,初始化器是一次性的,只为添加 acceptor
//7 绑定端口
serverSocketChannel.bind(new InetSocketAddress(8080));
//8 触发 channel active 事件,在 head 中关注 op_accept 事件
selectionKey.interestOps(SelectionKey.OP_ACCEPT);
其实就做了下面这五件事(五行代码 )
//打开selector
Selector selector = Selector.open();
//打开ssc
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//将channel绑定到selector上
SelectionKey selectionKey = serverSocketChannel.register(selector, 0, nettySsc);
// 绑定端口
serverSocketChannel.bind(8080, backlog);
//通过selectionKey关注一个可连接事件
selectionKey.interestOps(SelectionKey.OP_ACCEPT);
服务器代码
public class TestSourceServer {
public static void main(String[] args) {
new ServerBootstrap()
.group(new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new LoggingHandler());
}
}).bind(8080);
}
}

因为NioEventLoopGroup里面已经包含了selector,所以Selector selector = Selector.open(); 这行代码便不需要去看了
可以理解为是在NioEventLoopGroup里面打开或创建了selector
将上面的代码debug启动


doBind这个方法会在主线程里面运行
initAndRegister这个方法:init也是在主线程里面执行,会在两个线程里面进行执行,

initAndRegister会返回一个Future对象
doBind0这个方法的会在nio线程执行


进入newChannel方法



进入init方法


进入该方法


进入下面这个方法

在这个方法里面进行线程切换,

进入register0这个方法

进入doXXX这个方法,在这个方法里面进行注册(Nio线程注册)

返回上一级,在doxx下面有一行代码去进行调用之前的初始化handler方法

这个初始化的handler:向nio ssc加入了acceptor handler (在accept 事件发送后建立连接)

再回到最初的位置,这个位置的set便是给之前的promise设置结果,如此便会去执行doBind0这个方法


进入里面的内容


一直进入,直到这个位置,才是真正的使用

再进入,在这里执行了dobind0方法,绑定了8080端口

值执行完dobind0方法后,进行判断,如下

在这个handler上面进行关注accept事件
进入代码,直到这里才会去进行关注

NioEventLoop 既会处理IO事件 , 也会处理 普通任务 和 定时任务
NioEventLoop由 selector 和 一个线程 与 任务队列 组成
selector的位置:

线程位置:

任务队列位置:


在构造方法调用时创建


两个selector的区别:有两个是为了在遍历selectedKeys提高性能(selector使用数组)
当首次调用 execute 方法时启动
下面这个真正启动的方法是只会执行一次,通过state 状态位控制线程只会启动一次


当提交任务时,会就是selector阻塞,如下,去唤醒

只有其它线程提交任务时,才会调用selector 的wakeup方法

如果有多个其它线程都来提交任务,为了避免wakeup被频繁调用,保证在多线程线程下只能有一个线程对该布尔变量设置成功
Nio的线程会在一个run方法里面,进行死循环,死循环里面有select分支进行操作,

什么时候,进入SelectStrategy.SELECT分支去唤醒


通过上面的判断进入select方法后,会进入select方法

进入select方法

这个超时时间是如何计算的


所以阻塞的情况有三种:

jdk在linux的selector才会出现这个空轮询bug
nio空轮询bug发生时机:当调用无参的selector事件便会阻塞,或者是在带超时时间的selector方法时超时时间未到时阻塞,当这个bug发生时,即使没有事件,即使没有超时,这个selecor方法仍然会继续运行,不会阻塞住,一直在空转(因为这是一个死循环)

解决方法:使用一个计数selectCnt

如果大于一个预值(默认是512)

旧的会将数据复制到新的selector里面
如果有任务或是有事件发生,程序便会往下运行,进入if-else语句块
ioRatio:控制处理io事件所占用时间的比例(默认是50%:50%处理IO事件,50%处理普通任务)
ioRatio设置为100的作用:进入if分支,将所有的IO事件处理完毕,再进入finally,处理完所有的普通任务(没有设置超时)

以前selectedKeys默认的是set集合,遍历的性能较低
netty尝试使用数组的形式进行替换,这样在遍历的时候,效率更加高
体现在之前处理selectedKeys时

进入处理的能够方法

默认进入优化后的


对事件进行处理,判断是哪一种事件,分别进行读写处理

1)selector.select0 阻塞直到事件发生
2)遍历处理selectedKeys
3)拿到个key,判断事件类型是否为accept
4)创建SocketChannel,设置非阻塞
5)将SocketChannel注册至selector
6)关注selectionKey的read事件
前三步(123)在netty的EventLoop里面已经做了
后面三件事将会在下面这个方法里面做完

处理第四步的操作,在这里创建了NioSocketChannel


处理第五步操作



调用之前设置的一些初始化器

进行第六步:

启动流程和accept流程差不多
1)selector.select0 阻塞直到事件发生
2)遍历处理selectedKeys
3)拿到一个key, 判断事件类型是否为read
4)读取操作

