netty服务端启动总结

一、基础校验

  • 1.首先校验是否上传了group,也就是我们的nioeventloop线程池
  • 2.其次在看channelFactory是否为空,我们通过channel方法设置我们的服务端channel的class类型
  • 3.检测childHandler是否存在,当我们服务端接收到客户端连接之后,会再建立一个channel保持与客户端的通信。
    通信的处理则通过childChannel
  • 4.检测是否存在childgroup,对于服务端来说group主要是处理客户端的连接请求,而childgroup则是主要处理与客户端的读写事件。
    如果我们未填写,则将group赋值给childgroup。这有可能导致 我们与客户端通信如果存在阻塞操作,而该channel又恰好
    绑定在服务端监听客户端链接的nioeventloop上,这会到连接事件超时或者阻塞
  • 5.判断我们是否设置我们监听地址:localAddress

二、实例化channel

  • 1.一般是NioServerSocketChannel的class的实例化,会调用其构造函数
  • 2.构造函数先是获取SelectorProvider,其根据os的不同底层实现也不同
  • 3.然后通过该SelectorProvider生产一个对应的channel
  • 4.然后给当前的channel设置parent为null,设置一个id,unsafe(不是jdk的unsfae)和pipeline
  • 5.pipeLine会设置其channel,tail和head链表,还有voidPromise以及succeededFuture
  • 6.该channel默认携带的readInterestOp是read事件
  • 7.给channel设置为非阻塞
  • 8.设置该channel的config,会从我们的channel中获取javaSocket也就是ServerSocket,同时设置AdaptiveRecvByteBufAllocator
    因为我们服务端channel一般只是处理链接不发送消息 所以我们这边没有设置MaxBytesPerGatheringWrite
  • 9.AdaptiveRecvByteBufAllocator设置了buffer的最大值(默认65536byte),最小值(默认64byte),初始值(默认1025byte),以及每个消息循环读取的最大次数
  • 10.其中每个消息最大循环读取次数是从channel的meta里面获取defaultMaxMessagesPerRead得到的

三、初始化channel

  • 1.将我们代码里面设置的channelOption,即设置到channelConfig的属性里面
  • 2.给channel设置key-value属性,这样的话我们可以通过该channel传递一些我们需要的数据
  • 3.添加了一个ChannelInitializer,而addLast这个方法主要的逻辑是给当前的handler和group包装成
    一个HandlerContext,默认的group为空,如果我们设置了不为空则执行这个handler就从group获取executor执行
  • 4.将当前HandlerContext加入到链表中,然后判断是否还未注册,是否是外部线程来执行逻辑
  • 5.如果还未注册则设置当前的HandlerContext为pending状态,并添加一个任务到pendingHandlerCallbackHead链表
    ,等到注册成功后执行该链表任务,其任务主要逻辑就是调用handlerAdded方法然后设置handlerContext为completed状态
  • 6.如果是外部线程则将第五步骤包装成一个任务放入队列,由loop线程自己调度
  • 7,如果就是当前线程且channel已经注册了则直接调用handlerAdded方法,设置completed标识
  • 8.对于remove也是相似操作先从handlerContext链表中剔除,再执行handlerRemoved,然后设置removed标识
  • 9.对于默认的ChannelInitializer其主要是将我们先前配置的handler(注意不是childhandler)加入到pipeline
    并且添加了一个任务放入到队列中,该任务主要就是添加ServerBootstrapAcceptor,其会专门处理连接请求,获取客户端传递过来的
    channel,给该channel添加childHandler,并且将该childchannel注册到我们childGroup上的一个loop线程上。

四、注册channel

  • 1.从group中拿一个nioeventloop,将我们的服务端channel注册到上面。

  • 2.获取loop的方式又两种一种是:Math.abs(idx.getAndIncrement() % executors.length),还有一个是
    idx.getAndIncrement() & executors.length - 1

  • 3.然后将channel和loop包装成DefaultChannelPromise,并从channel中获取unsafe调用其register方法进行实际注册

  • 4.然后判断当前线程是否和loop中的线程一致,如果不一致则将注册方法报过程一个任务放入到loop的任务队列执行然后直接返回promise

  • 5.具体的注册则是先设置该promise不可以取消,如果设置失败再检测channel是否open,不open则关闭

  • 6.将当前的channel(不是netty的nioServerSocketChannel,是jdk的serverSocketChannel)注册到我们loop中的selector上,携带我们当前的nioServerSocketChannel初始化注册感兴趣的事件=0,即对任何事件都不敢兴趣

  • 7.注册成功之后返回的selectionKey,是一个token标识。我们可以通过他获取该channel发生的事件等等。

  • 8.然后我们invokeHandlerAddedIfNeeded,因为我们在未注册成功前就设置了handlerPedding状态 需要在注册成功之后执行handlerAdded(我们的ChannelInitializer的initChannel会在handlerAdded方法调用结束后调用,该方法主要就是执行initChannel然后把ChannelInitializer从HandlerContext链表中删除,同时我们在channelRegistered也会尝试判断是否已经调用了如果没有则也会调用initChannel)

  • 9.然后在整个pipeline里面传播ChannelRegistered事件,即从head开始传递到tail,其中还会判断当前的handler是否已经是added状态不是的话跳过寻找下一个,是的话就执行channelRegistered方法

  • 10.然后再判断当前channel是否已经Active(这里active的条件是注册然后被连接了),如果已经Active且是第一次则再激活整个通道的ChannelActive事件,
    对于重复注册的channel则会判断是已经开启autoread,如果开启就直接执行beginRead,不会执行ChannelActive

  • 11.ChannelActive是从head传播到tail,传播完成执行开始channel.read(),实际上就是tail的read方法,最终调用调用到head的beginRead()

  • 12.beginRead:该方法主要是检测SelectionKey是否有效,然后设置readPending=true,注册我们该channel的感兴趣事件,我们初始化都是注册0,但是不同的netty的channel 会再初始的时候携带自己感兴趣的事件如accept(服务端)或者read(客户端)

  • 13.注意再注册chanel这个方法里面的isActive主要是 判断客户端传递到服务端的channel是否已经激活,因为该channel不需要绑定,所以可以直接激活 而对于客户端的channel或者服务端的channel都是再绑定之后再激活

五、开始绑定监听端口

  • 1.首先检测一个情况:如果套接字未绑定到通配符地址,则非root用户无法接收广播数据包。无论如何,根据请求绑定到非通配符地址(/127.0.0.1:8080)。
  • 2.检测是已经激活
  • 3.开始绑定监听端口,绑定时候传递了我们的backLog,这个是完全队列的大小
  • 4.如果是第一次激活则调用fireChannelActive方法。

猜你喜欢

转载自blog.csdn.net/weixin_34162228/article/details/87461949