netty--源码流程--结合NIO点位
2021-07-20 12:05:31 0 举报netty源码解析,启动流程解析,netty架构,netty原理
netty原理
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
收藏
立即使用
收藏
立即使用
收藏
立即使用
收藏
立即使用
Collect
Get Started
Collect
Get Started
Collect
Get Started
Collect
Get Started
评论
0 条评论
下一页