• 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);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    其实就做了下面这五件事(五行代码 )

    //打开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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述
    因为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)读取操作

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

  • 相关阅读:
    javascript入门(引入方式,数据类型,变量)
    云原生Service Mesh服务网格简单介绍
    DFS-4-N皇后问题
    架构核心技术之微服务架构
    Scala的函数式编程与高阶函数,匿名函数,偏函数,函数的闭包、柯里化,抽象控制,懒加载等
    群狼调研(长沙医院满意度调查)|如何做医疗服务满意度调查
    『Linux升级路』基础开发工具——vim篇
    vue高频面试题
    软件测试常见术语和名词解释
    国产蓝牙耳机什么牌子好?2022蓝牙耳机品牌排行
  • 原文地址:https://blog.csdn.net/yyuggjggg/article/details/126634821