Netty(13)源码分析(一)

服务器启动流程

Nio是怎么启动的

//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);

Netty将上面的五步进行处理

服务器代码


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

doBind这个方法会在主线程里面运行

initAndRegister这个方法:init也是在主线程里面执行,会在两个线程里面进行执行,
在这里插入图片描述

initAndRegister会返回一个Future对象

doBind0这个方法的会在nio线程执行

在这里插入图片描述

init

创建NioSocketchannel

在这里插入图片描述
进入newChannel方法

在这里插入图片描述

在这里插入图片描述

添加初始化handler

在这里插入图片描述
进入init方法
在这里插入图片描述

register(切换线程)

在这里插入图片描述
进入该方法
在这里插入图片描述
在这里插入图片描述
进入下面这个方法
在这里插入图片描述
在这个方法里面进行线程切换,
在这里插入图片描述

进入register0这个方法
在这里插入图片描述
进入doXXX这个方法,在这个方法里面进行注册(Nio线程注册)
在这里插入图片描述
返回上一级,在doxx下面有一行代码去进行调用之前的初始化handler方法

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

在这里插入图片描述

doBind0方法运行

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

在这里插入图片描述

在这里插入图片描述
进入里面的内容
在这里插入图片描述
在这里插入图片描述
一直进入,直到这个位置,才是真正的使用
在这里插入图片描述
再进入,在这里执行了dobind0方法,绑定了8080端口
在这里插入图片描述

关注accept事件

值执行完dobind0方法后,进行判断,如下
在这里插入图片描述
在这个handler上面进行关注accept事件

进入代码,直到这里才会去进行关注

在这里插入图片描述

NioEventLoop

NioEventLoop 既会处理IO事件 , 也会处理 普通任务 和 定时任务

NioEventLoop由 selector一个线程任务队列 组成

selector的位置:

在这里插入图片描述

线程位置:
在这里插入图片描述

任务队列位置:

在这里插入图片描述
在这里插入图片描述

selector创建时机

在构造方法调用时创建
在这里插入图片描述
在这里插入图片描述

两个selector的区别:有两个是为了在遍历selectedKeys提高性能(selector使用数组)

  1. unwrappedSelector:原始的selector(基于hash表的实现)
  2. selector:包装后的selector(使用数组)

selector启动

当首次调用 execute 方法时启动

下面这个真正启动的方法是只会执行一次,通过state 状态位控制线程只会启动一次
在这里插入图片描述

selector阻塞

在这里插入图片描述
当提交任务时,会就是selector阻塞,如下,去唤醒
在这里插入图片描述

wakeup方法

只有其它线程提交任务时,才会调用selector 的wakeup方法
在这里插入图片描述

wakenUp变量作用

如果有多个其它线程都来提交任务,为了避免wakeup被频繁调用,保证在多线程线程下只能有一个线程对该布尔变量设置成功

select 分支

Nio的线程会在一个run方法里面,进行死循环,死循环里面有select分支进行操作,

在这里插入图片描述

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

  1. 当没有任务时,才会进入SelectStrategy.SELECT
  2. 当有任务时,会调用selectNow方法,同时拿到 io事件在这里插入图片描述
    在这里插入图片描述

select阻塞多久

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

在这里插入图片描述
进入select方法

在这里插入图片描述
这个超时时间是如何计算的
在这里插入图片描述
在这里插入图片描述

所以阻塞的情况有三种:

  1. 阻塞1s多一点
  2. 有任务
  3. 有事件
    在这里插入图片描述

nio空轮询bug

jdk在linux的selector才会出现这个空轮询bug

nio空轮询bug发生时机:当调用无参的selector事件便会阻塞,或者是在带超时时间的selector方法时超时时间未到时阻塞,当这个bug发生时,即使没有事件,即使没有超时,这个selecor方法仍然会继续运行,不会阻塞住,一直在空转(因为这是一个死循环)
在这里插入图片描述
解决方法:使用一个计数selectCnt

在这里插入图片描述
如果大于一个预值(默认是512)

在这里插入图片描述
旧的会将数据复制到新的selector里面

ioRatio

如果有任务或是有事件发生,程序便会往下运行,进入if-else语句块

ioRatio:控制处理io事件所占用时间的比例(默认是50%:50%处理IO事件,50%处理普通任务)

  1. ioTime:代表执行io事件处理耗费的时间,
  2. runAllTasks:运行普通任务(这里面对之前的数进行运算,获取运行普通任务的时间是多长)

ioRatio设置为100的作用:进入if分支,将所有的IO事件处理完毕,再进入finally,处理完所有的普通任务(没有设置超时)

在这里插入图片描述

selectedKeys的优化

以前selectedKeys默认的是set集合,遍历的性能较低

netty尝试使用数组的形式进行替换,这样在遍历的时候,效率更加高

体现在之前处理selectedKeys时
在这里插入图片描述
进入处理的能够方法
在这里插入图片描述
默认进入优化后的
在这里插入图片描述

在这里插入图片描述
对事件进行处理,判断是哪一种事件,分别进行读写处理

在这里插入图片描述

netty的accept流程

Nio的

1)selector.select0 阻塞直到事件发生
2)遍历处理selectedKeys
3)拿到个key,判断事件类型是否为accept
4)创建SocketChannel,设置非阻塞
5)将SocketChannel注册至selector
6)关注selectionKey的read事件

netty的

前三步(123)在netty的EventLoop里面已经做了

后面三件事将会在下面这个方法里面做完

在这里插入图片描述
处理第四步的操作,在这里创建了NioSocketChannel

在这里插入图片描述
在这里插入图片描述

处理第五步操作
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
调用之前设置的一些初始化器
在这里插入图片描述
进行第六步:
在这里插入图片描述
启动流程和accept流程差不多

read流程

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

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

?abc!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值