netty--源码流程--结合NIO点位
2021-07-20 12:05:31 0 举报
netty源码解析,启动流程解析,netty架构,netty原理
作者其他创作
大纲/内容
NIO里面设置非阻塞
循环结束:传递到 ChannelPipeline 的 HeadContext 类的 write () 方法
HeadContext
得到
.childHandler给worker循环组使用的Handler
通过反射创建一个 NioServerSocketChannel:将 Java 原生 Channel 绑定到 NettyChannel 中注册 Accept 事件为 Channel 分配 id为 Channel 创建 unsafe为 Channel 创建 ChannelPipeline(默认是 head<=>tail 的双向链表)
channelFuture.channel().closeFuture().sync();关闭也是会在完全关闭才会返回
pipeline.fireChannelRegistered();
io.netty.channel.socket.nio.NioServerSocketChannel#doClosejavaChannel().close();关闭 Java 原生的 Channel;
主要作用:channelRead实现将Channel转移到workerGroup
io.netty.util.concurrent.SingleThreadEventExecutor#execute
int selectedKeys = selector.select(timeoutMillis);
GenericEventExecutorChooser
AbstractChannel.AbstractUnsafe#flush调用flush0();也就是AbstractNioUnsafe#flush0--》AbstractChannel.AbstractUnsafe#flush0--》doWrite(outboundBuffer);
如果设置的时候使用了handler(),也就是给bossGroup设置了Handler,就加到管道里面。ChannelInitializer的initChannel在Channel注册到eventGroupLoop上面的时候会被调用,而且当前ChannelInitializer(并不是Handler,但是可以重写他的initChannel这个方法添加多个Handler,这就是他的使命,一次性添加多个Handler。实际也是这样做的)会被删除,因为没用了最后往管道添加一个接收器ServerBootstrapAcceptor,可以看到事件循环组其实就是一个线程池,execute方法里面是添加ServerBootstrapAcceptor接收器,对应的就是reactor里面的Acceptor
写数据请求解除阻塞SelectionKey.OP_READ
SelectionKey.OP_READ | SelectionKey.OP_ACCEPT
异步执行
Channel的pipeline
doRegister();
策略模式
startThread();放到队列之后要启动这个eventLoop
服务启动完成后,只有一个 Channel,也就是 NioServerSocketChannel。ChannelPipeline有head<=>LoggingHandler<=>ServerBootstrapAcceptor<=>tail,所以,我们只要把断点打在这四个 Handler 中的任意一个的 channelRead () 方法中即可。
建立连接详细版
都可以在启动类设置
不管是serverSocketChannel还是SocketChannel,一开始都是简单的初始化器。注册结束之后会被替换为真正的Hhandler
for (;;) {死循环监听、处理事件
taskQueue.offer(task);
一般来说,服务端发送数据不会在每次 write () 的时候就发送出去,而是先缓存起来,等到一定量之后或者显式地说明要发送的时候再真正地发送出去
接收
font color=\"#ff0000\
把数据添加到 ChannelOutboundBuffer 缓存中;
异步注册成功结束之后Java 原生 Channel 绑定到一个本地地址上
NioEventLoopGroup 怎么知道所有的 NioEventLoop 都关闭成功
循环调用所有孩子的(NioEventLoop)shutdownGracefully()、io.netty.util.concurrent.SingleThreadEventExecutor#shutdownGracefully
优雅关闭详细版
TailContext
for循环处理selectedKeys所有的key。取出SelectionKey中的附件,服务启动的时候把Selector、Java原生Channel、Netty的Channel绑定在一起了,新连接建立的过程,取出来Netty中的NioServerSocketChannel,也就是下面的 a 参数
channelFactory.newChannel()
ch.configureBlocking(false);非阻塞
删除的逻辑就是会先调用自己写的全部注册进去之后,再调用原本的内部的一个方法将自己从pipeline里面删除handlerAdded--》initChannel--》remove(ctx);
main返回的Future
注册到 EventLoop 中同样也会与这个 EventLoop 中的 Selector 绑定,Netty 的 NioSocketChannel 同样地也是以附件的形式绑定在 SelectionKey 中;最后回调初始化器注册Handler
serverSocketChannelsocketChannel最后都是调用这个下面的流程是两种Channel共享的
设置子Channel的配置等信息,Options和Attribute
selectCnt ++;空轮询bug解决
io.netty.bootstrap.ServerBootstrap#init(channel)
netty 接收新的连接流程解析
LoggingHandler#bind打印日志,之后调用span style=\"font-size: inherit;\
JDK NIO 的bind
io.netty.bootstrap.ServerBootstrap.ServerBootstrapAcceptor#channelRead在服务器初始化之后注册的Handler,也就是reactor中的Acceptor
2. 从buffer里面获取需要处理的Channel
重点:去注册了,和服务端共用一套逻辑
select(wakenUp.getAndSet(false));
自己定义的Handler
规避多线程并发问题是当前线程就直接执行,不是当前线程就会作为一个任务加到当前事件循环,等待被当前事件循环的IO线程执行,所以就是把多线程串行 了,避免了并发的问题netty的任务队列保证先进来的队列先执行,综上,netty中Channel的实现是线程安全的。所以可以保存一个Channel引用,想要向远程断点发数据时候,可以用这个引用调用Channel的方法,很多线程再使用也不会有多线程问题而且一定会按照顺序发出去。
io.netty.util.concurrent.SingleThreadEventExecutor#doStartThread
设置pipeline流转需要的AttributeKey以及value注意作用域,下面有写
Channel注册到一个线程(事件循环组里面)
unsafe.read();read2个实现
处理连接请求SelectionKey.OP_ACCEPT
新建EventLoopGroup设置一些参数的值,主要是设置了全局的Selector
io.netty.util.concurrent.MultithreadEventExecutorGroup#MultithreadEventExecutorGroup(的构造器里面为每一个child(eventLoop)有一个监听器,自己所有的child也就是所有管理的eventLoop都结束的时候,就将自己的terminationFuture设置为true
NIO
1
AbstractChannelHandlerContext#writefont color=\"#ff0000\
ServerSocketChannel注册的时候,线程是main,而eventLoop是新开的,所以会进入else里面
concurrent.SingleThreadEventExecutor#shutdownGracefully其它线程正在执行关闭,直接返回,否则新状态为ST_SHUTTING_DOWN。添加一个空任务,来唤醒EventLoop。实际上是唤醒的 selector,也就是 selector.select () 的位置;
我们自己写的服务端真正的业务逻辑Handler注册不在这里,在客户端建立连接之后得到SocketChannel之后才会注册,这个只是加一个初始化器,和serverinit时候一样
NioEventLoop 的 run () 方法中,select接收到开始处理,向下执行
写出数据的缓存置空,不允许再写出数据;缓存中未发送的数据将失败;关闭 Java 原生的 Channel;closeFuture 置为关闭状态;取消 Channel 关联的 SelectionKey;调用 channelInactive () 和 channelDeregister () 方法;
开始写回数据
优雅关闭总结版
HeadContext,不仅是一个 ChannelHandlerContext,也是一个 ChannelInboundHandler,同时也是一个 ChannelOutboundHandler。TailContext,不仅是一 ChannelHandlerContext,同时也是一个 ChannelInboundHandler我们的Handler处理之后,没有调用 ChannelHandlerContext 的 fireChannelRead () 方法,所以,不会再调用到下一个 ChannelHandlerContext,也不会走到 TailContext TailContext 的 channelRead () 方法,只打印了几行日志,最后消息被丢弃了。对资源做一个回收,最后一行释放了 ByteBuf 的引用,对于池化的 ByteBuf,可以让它们重新回到池中,对于非池化的 ByteBuf,可以释放它们占用的内存
Executor executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());每一个任务的线程执行器
设置ChannelOptionTCP 相关的系统设置项
super(parent);
循环结束:传递到我们自己的Handler的时候,就会循环执行结束
io.netty.channel.nio.NioEventLoop#run 会检测如果有任务,调用的是selectNow(),也就是不阻塞如果没有任务,才会下面的select(),默认是阻塞的通过前面的唤醒,唤醒的是下面的select()往下走到isShuttingDown()
this(newSocket(DEFAULT_SELECTOR_PROVIDER)new...会创建返回一个JDK 的 serverSocketChannel
PooledByteBuf#setBytes
bind(new InetSocketAddress(inetPort));
处理读取数据
next() 选哪一个注册对于server而言一般只有一个
之前所有全是main线程在执行
next得到一个事件循环(一个线程):(EventLoop) EventExecutor
public class EchoServerHandler extends font color=\"#ff0000\
select的时候就会将自己的Socket四元组的fd注册到epoll_ctl ,之后wait
启动结束返回Future
Acceptor
FastThreadLocal.removeAll();
pipeline.invokeHandlerAddedIfNeeded();调用已经加进来的Handler,就是最开始的ChannelInitializer
pipeline
socketChannel完成注册的时候会替换为真正的Handler
io.netty.channel.nio.AbstractNioMessageChannel.NioMessageUnsafe#read
ch.configureBlocking(false);
是JDK NIO 的Selector.close()
这里EventLoop的名字也表示自己是一个线程的包装,只有事件循环才有真正注册Channel的方法SingleThreadEventLoop#register(io.netty.channel.Channel)
MultithreadEventLoopGroup
1 同步调用
register0(promise)注册Channel到Selector
netty 优雅关闭流程解析(绕,主要是每个EventLoop的Future和整个EventLoopGroup的Future之间的相互关联 以及 剩余任务的执行)
这里是管道是NioServerSocketChannel 对应的 ChannelPipeline:::head<=>LoggingHandler<=>ServerBootstrapAcceptor<=>tail
反射的方式创建Channel,class在前面已经设置到里面了,不带参数的,直接无参构造器的newInstance
final ChannelFuture regFuture = initAndRegister();异步的初始化和注册
用户主线程中调用这个方法,判断 Group 完全终止后做些什么事情
// 添加String类型的编解码p.addLast(new StringDecoder());// 因为还要写回客户端,所以还要加上StringEncoder p.addLast(new StringEncoder());p.addLast(echoServerHandler);
调用 shutdownGracefully (),静默周期,默认2秒超时时间,默认15秒
read()方法的参数是JDK NIO 的ByteBuffer
register(channel)
Channel注册到事件循环组
只传进来了一个端口,而使用 InetSocketAddress 类构造了一个地址,默认的,会生成一个 0.0.0.0:inetPort 的地址
初始化Channel,添加了ChannelInitializer,后续Channel注册时候使用
io.netty.channel.socket.nio.NioServerSocketChannel#doReadMessages(readBuf)这个读数据是自己写到里面一个Channel
Netty 中轮询的方法是写在 NioEventLooop 中的;
io.netty.channel.nio.NioEventLoop#run
真正启动线程的地方,也就是 doStartThread () 方法清理了所有 ThreadLocal 相关的资源,最后把 NioEventLoop 的状态设置为 ST_TERMINATED
ChannelHandlerB
注意socketChannel注册到worker循环组和serveSocketChannel注册到boss的逻辑是一样的,最后也会调用invokeHandlerAddedIfNeeded将ChannelInitializer替换为我们自己编写的一个个Handler
netty 接收新的数据流程解析
启动总结版
eventLoop.execute其实就是Executor的execute方法
开始flush数据
获取这里初始化的只有首尾的pipeline
前面的都是在设置属性值,最后的bind才是真正的开始读取了父子事件循环的配置以及其他所有的配置,new出来了ServerBootstrap
3. pipeline.fireChannelRead(byteBuf);触发 ChannelPipeline 中的 ChannelHandler 的 channelRead () 方法
socket Channel 的 pipeline
观察线程栈
正常来说,服务是不会走到第 10 步的,除非出现异常,因为第 9 步的 sync () 会阻塞 main 线程。先把第 9 步注释掉,让程序能够走到第 10 步
返回Future的时候一般异步注册Channel到eventLoop还没有结束,就加一个监听器,结束时候回调下面
连接请求解除阻塞SelectionKey.OP_ACCEPT
io.netty.channel.AbstractChannel.AbstractUnsafe#register
closeAll () 主要是对 Channel 的关闭,跟 Channel 相关的资源释放都在这里,比如缓存消息的失败、SelectionKey 的取消、Java 原生 Channel 的关闭等;
debug方法
((ChannelInboundHandler) handler()).font color=\"#ff0000\
反射创建的时候就会使用
JDK NIO 的register
Channel注册了之后才会被调用
循环
AbstractChannelHandlerContext.font color=\"#ff0000\
客户端
异步把 Channel 绑定到一个 EventLoop 上把 Java 原生 Channel、Netty 的 Channel、Selector 绑定到 SelectionKey 中触发 Register 相关的事件
反射创建
MultithreadEventExecutorGroup
启动详细版
2
p.addLast(new ChannelInitializer<Channel>() {ChannelInitializer 本质是一个InBoundHandler
SelectionKey.OP_WRITE
ChannelInitializer有什么用?不是Handler,但是他的方法可以一次性添加多个Handler,添加完了没用了就可以删除了
解码
if
netty高性能网络框架原理&源码解析By deltaqin
serverSocketChannel.accept();
pipeline = newChannelPipeline();
给新连接建立NioSocketChannel之后加到readBuf里面
ServerBootstrapAcceptor
ch
socketChannel注册到事件循环组的Selector
2. 读取数据到 ByteBuf 中doReadBytes(byteBuf)设置可读取的长度。调用ByteBuf的writeBytes()方法,第一个参数是Java原生的SocketChannel,第二个参数是可读取的长度
main线程真正返回的Future
pipeline.fireChannelRead (msg) 触发下一个 ChannelHandlerContext 的调用;ChannelHandlerContext.invokeChannelRead(m)调用到下一个 ChannelHandlerContext;((ChannelInboundHandler) handler()).font color=\"#ff0000\
把 ServerBootstrap 中的配置设置到 Channel 中添加 ServerBootstrapAcceptor 这个 Handler
bind(9000).sync();非阻塞方法调用
AbstractChannel.AbstractUnsafe#bind里面开始真正 doBind(localAddress);
runAllTasks();
new ServerBootStrap
反射创建NioServerSocketChannel,源码可知其实就是多了配置属性的JDKServerSocketChannel
内部其实是调用了每个 NioEventLoop 的 shutdownGracefully () 方法,最后返回了 NioEventLoopGroup 的 terminationFuture
循环执行结束:传递到我们自己的Handler的时候,就会循环执行结束,不会继续向下fireChannelRead了,这里没有写回数据积就结束了,有写回数据就开始调用write逻辑了
DefaultChannelPipeline
把数据从 ChannelOutboundBuffer 取出来;调用 Java 原生的 SocketChannel 把数据发送出去。
try { // 省略其他代码 // 9. 等待服务端监听端口关闭,这里会阻塞主线程 f.channel().closeFuture().sync();} finally { // 10. 优雅地关闭两个线程池 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully();}
连接建立的socketChannel注册到workGroup上的一个线程的Selector上
阻塞等待
childGroup.register(child)
ChannelInitializer
和Channel互相引用
对于接收数据,如果需要数据在 ChannelPipeline 中传递,就调用 ctx.fireChannelRead(msg) 方法
processSelectedKeys()
channelRead方法调用
pipeline = newChannelPipeline();创建 ChannelPipeline;
接收数据详细版
ChannelPipeline
NioSocketChannelsuper(parent);
NioServerSocketChannel#doBind
childGroup.register(child)
NioSocketChannel#doWritefont color=\"#ff0000\
线程池执行,异步执行真正的在个注册executor.execute(new Runnable() {SingleThreadEventExecutor.this.run();
常量是SelectorProvider.provider();这是NIO获取Selector的方式,spi的方式创建。里面是同步的,可能会大量连接的时候阻塞,所以直接把这个provider存起来provider就是jdk的 Selector需要Channel的时候直接调用openServerSocketChannel而不是SocketChannel的open方法避免性能问题issues/2308newSocket方法使用Java底层的SelectorProvider创建一个Java原生的ServerSocketChannelprovider.openServerSocketChannel();
所有的child也就是所有管理的eventLoop都结束的时候,就将自己的terminationFuture设置为true
AbstractEventExecutorGroup#shutdownGracefully()第一个参数为静默周期,默认2秒,第二个参数为超时时间,默认15秒
MultithreadEventExecutorGroup#shutdownGracefully调用孩子的shutdownGracefully(),也就是当前Group的所有EventLoop。EventExecutor无疑就是NioEventLoop
io.netty.channel.nio.AbstractNioChannel#doRegister
从尾开始调用,也就是font color=\"#ff0000\
触发执行task.run
confirmShutdown ()对队列中的任务或者钩子任务进行处理,主要是通过一个叫做静默周期的参数来控制尽量执行完所有任务,但是,也不能无限期等待,所以,还有一个超时时间进行控制
ChannelInitializer入站处理器
注册Channel到SelectorNIO调用
pipeline.fireChannelRead(readBuf.get(i));
threadLock.countDown()某个地方有个 await() 方法等着
触发添加Handler的回调,其中pineline.addLast(ChannelInitializer)的处理就是在这一步完成的启动时候的serverSocketChannel注册而言 这一步之后pipeline里面应该是head<=>LoggineHandler<=>tail。而ServerBootstrapAcceptor还没有加入到pipeline中,因为它设置了使用EventLoop的线程执行,当前线程就是EventLoop的线程。所以,添加ServerBootstrapAcceptor会在当前任务执行完毕才会执行
doBind0 ()
设置EventLoopGroupworkerGroup(定义在ServerBootStrap)
io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#read
JDK NIO 的select
ServerSocketChannel
sync确保初始化注册都结束了,操作完毕 ,返回的channelFuture异步操作结束虽然是异步的,但是也要有同步操作
通过 ServerBootstrapAcceptor 这个 ServerSocketChannel的的pipeline的 ChannelHandler 来给SocketChannel添加初始化器,之后初始化 NioSocketChannel 的配置并将其注册到 EventLoop 中;ServerBootstrapAcceptor#channelRead
NioEventLoop#cleanup()里面selector.close();
DefaultChannelPipeline.HeadContext#channelRead里面的ctx.fireChannelRead(msg);触发下一个Context中Handler的调用
CAS 自旋 更新当前的eventLoop的状态为关闭
处理连接的建立
此时pipeline中的Handler为head<=>LoggingHandler<=>ServerBootstrapAcceptor<=>tail,出站的pineple实际为tail=>LoggingHandler=>head
是JDK NIO 的Channel.close()
Netty 底层也是通过 Java 原生 ServerSocketChannel 来接收新连接的;
NioEventLoopGroup
main线程返回
netty 写出数据流程解析
ChannelHandlerContext 1
1. 通过 allocator 创建一个 ByteBuf;默认地,通过各种参数判断当前操作系统是否允许池化、Unsafe、堆外这三个指标;也可以通过启动参数来控制
closeFuture 置为关闭状态;
NioEventLoop事件循环这个线程执行的代码其实是一个死循环serverSocketChannelsocketChannel最后都是调用这个
实际是调用的各个ChannelHandler的channelRegistered()方法
ChannelFuture regFuture = config().group().register(channel);config返回服务器启动类的config对象,group获取到事件循环组
serversocketChannel注册到事件循环组的Selector
ChannelHandlerA
channel.eventLoop().execute(new Runnable() { font color=\"#ff0000\
netty的Handler
关闭
JDK NIO 的Selector在这使用
建立连接总结版
成功激活,调用pipeline.fireChannelActive()方法,设置promise为成功状态 safeSetSuccess(promise);
放到任务队列等待被执行
NioServerSocketChannel
select方法得到事件之后开始判断是accept事件,调用 NioMessageUnsafe#read
AbstractChannelHandlerContext#findContextInbound循环执行 ctx = AbstractChannelHandlerContext.next;将所有的Handler连起来,返回链表头部
它的父亲,也就是 NioEventLoopGroupconcurrent.MultithreadEventExecutorGroup#awaitTermination里面循环每一个NioEventLoop,等待它们终止,就返回整个Group的终止状态
ChannelPipeline p = channel.pipeline();一个个的Handler在这里
next().register(channel);
写出数据详细版
if (selectionKey.isReadable()) { // 强制转换为SocketChannel SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); // 创建Buffer用于读取数据 ByteBuffer buffer = ByteBuffer.allocate(1024); // 将数据读入到buffer中(第二阶段阻塞) int length = socketChannel.read(buffer); // 处理数据。。。}
DefaultChannelPipeline.HeadContext#flush调用unsafe.flush();
JDK NIO 的accept
断点打在 HeadContext 的 channelRead () 方法中。HeadContext,即是一个 ChannelHandlerContext,又是一个 ChannelInboundHandler,同时也是 ChannelOutboundHandler。待服务启动完成后,再在 HeadContext 的 channelRead () 方法中打一个断点, telnet localhost 8007
一般不会走到TailContext
init(channel)
hashmap的方式
this将当前 Netty 的 Channel 通过 attachment 的形式绑定到 SelectionKey 上把 Java 原生 Channel、Netty 的 Channel、Selector 绑定到 SelectionKey 中
ChannelHandlerContext 2
write()
实现了任务和线程创建解耦
非阻塞监听IO事件
ctx.flush(); 的调用循环过程跟 ctx.write(msg) 是类似的,直接来到 HeadContext 的 flush() 方法
next.invokeChannelRead(m);
初始化pipeline
返回NioEventLoop的terminationFuture
SingleThreadEventLoop
channel = channelFactory.newChannel();实际是ReflectiveChannelFactory反射新建Channel
组等待每一个成员都终止
register(new font color=\"#ff0000\
4. 触发 ChannelPipeline 中的 ChannelHandler 的 channelReadComplete () 方法
传递
offerTask(task)
JDK NIO 的Selector在new 的时候作为全局变量创建了
netty 启动流程解析
ch.unsafe().forceFlush();
io.netty.channel.AbstractChannel.AbstractUnsafe#write过滤消息,计算消息的大小,链表追加的方式添加到缓存ChannelOutboundBuffer中,并没有真正地发送出去。
处理读数据请求SelectionKey.OP_READ
图中红色就是使用到NIO的地方
flush()
.channel反射的方式创建,但是此时不创建,只是NioServerSocketChannel.class给了
Read事件或者Accept事件
多态找不到调用就看这个继承了谁
SocketChannel ch = SocketUtils.accept(javaChannel());
io.netty.util.concurrent.SingleThreadEventExecutor#confirmShutdown如果 confirmShutdown() 返回 true,将跳出循环,那么,这个 run () 方法也就结束了,如果返回 false,将重新执行这里面的逻辑,直到返回 true(也就是没有任务或者超时了)。返回到哪里了,也就是调用run的地方,看启动的时候谁异步开启这个eventLoop来注册Channel的,就是那里
接收数据总结版
ServerBootstrapAcceptor#channelRead
MultithreadEventLoopGroup# register(Channel channel)因为NioEventLoopGroup继承了这个类
循环执行结束:最后一定会走到 ChannelPipeline 的 HeadContext 类的 write () 方法
是JDK NIO 的Channel.write
设置EventLoopGroupbossGroup用于接收客户端连接(定义在AbstractBootStrap)
SocketChannel ch = SocketUtils.accept(javaChannel());使用java原生的方式获取SocketChannel
addTask(task);
开始真正启动
doBind(localAddress);
添加一个空任务,唤醒EventLoop的 selector,也就是 selector.select () 的位置;taskQueue.offer(WAKEUP_TASK);
ch 成员变量赋值,即是netty的ServerSocketChannel其实里面包含了NIO的Channel
Channel.pipeline().addLast(childHandler);给SocketChannel添加初始化器
register0(promise) 任务放到队列异步执行
serverbootstrap
NioEventLoop#closeAllSelectionKey里面的附件是放进去的Channel,取出来放一个集合,之后遍历这个集合一个个关闭AbstractChannel.close()
首先会执行和上面一样的循环,循环执行结束:最后一定会走到 ChannelPipeline 的 HeadContext 类的 flush () 方法
AbstractByteBuf#writeBytes
这不会OS 多路复用epoll_ctl,select才会
Netty 给 socketChannel 分配 NIO event loop 的规则是什么?从 EventExecutor 数组中选择一个 EventExecutor, 这里就是 NioEventLoop只有一个线程的线程池数组里面选一个“线程池”(事件循环)来注册socketChannel,选择的index计算有两种方式,根据初始的线程的个数是否是2的幂决定使用哪一个chooser,决定了index的计算方式不一样childGroup.register(child)--》next().register(channel); --》chooser.next();
如果是Write事件
1. readBuf里面填充需要处理的socketChannel
SingleThreadEventExecutor#doStartThread返回到这里真正启动线程的地方,也就是 doStartThread () 方法,此时还在eventLoop的线程里面,最后一次运行confirmShutdown()方法,把剩余的任务全部运行完,关闭selector,移除所有的threadLocal,更新状态为ST_TERMINATED,NioEventLoop的terminationFuture已成功
ServerBootstrap
JDK NIO 的Channel
cleanup ()对 Selector 进行关闭
写出数据总结版
main真正返回的Future
0 条评论
下一页