• 源码解析-Netty源码之Channel的rigister过程


     从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

     
  • 相关阅读:
    C# Dapper 2.0源码
    C#实现隐藏手机号、邮箱、姓名等敏感信息扩展方法
    C# 自定义弹窗提醒
    用sqlyog 连 mysql 8 时,报错:plugin caching_sha2_password could not be loaded
    汇总最全的C#.NET(数据库/.net/其它)面试题及参考答案
    安装sql2008r2后,数据库引擎为空是为什么?
    SQL Server 2008找不到SQL Server配置管理器的问题
    PrintDialog.ShowDialog不能显示打印对话框
    PrintDialog控件
    PrintPreviewControl控件
  • 原文地址:https://www.cnblogs.com/ttaall/p/14212079.html
Copyright © 2020-2023  润新知