一、基础校验
- 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 - 13.然后将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,不会执行ChannelActive11.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方法。