共计 4650 个字符,预计需要花费 12 分钟才能阅读完成。
作者:fredalxin\
地址:https://fredal.xin/netty-process
本文基于版本 4.1.46,同时只形容类而不展现具体源码。
Netty 的整体流程
Netty 的整体流程相对来说还是比较复杂的,初学者往往会被绕晕。
所以这里总结了一下整体的流程,从而对 Netty 的整体服务流程有一个大抵的理解。从性能上,流程能够分为服务启动、建设连贯、读取数据、业务解决、发送数据、敞开连贯以及敞开服务。
整体流程如下所示 (图中没有蕴含敞开的局部):
服务启动
服务启动时,咱们以 example 代码中的 EchoServer 为例,启动的过程以及相应的源码类如下:
EchoServer#new NioEventLoopGroup(1)->NioEventLoop#provider.openSelector()
: 创立 selectorEchoServer#b.bind(PORT).sync->AbstractBootStrap#doBind()->initAndRegister()-> channelFactory.newChannel() / init(channel)
: 创立 serverSocketChannel 以及初始化EchoServer#b.bind(PORT).sync->AbstractBootStrap#doBind()->initAndRegister()-> config().group().register(channel)
:从 boss group 中抉择一个 NioEventLoop 开始注册 serverSocketChannelEchoServer#b.bind(PORT).sync->AbstractBootStrap#doBind()->initAndRegister()->config().group().register(channel)->AbstractChannel#register0(promise)->AbstractNioChannel#javaChannel().register(eventLoop().unwrappedSelector(), 0, this)
: 将 server socket channel 注册到抉择的 NioEventLoop 的 selectorEchoServer#b.bind(PORT).sync()->AbstractBootStrap#doBind()->doBind0()->AbstractChannel#doBind(localAddress)->NioServerSocketChannel#javaChannel().bind(localAddress, config.getBacklog())
: 绑定地址端口开始启动EchoServer#b.bind(PORT).sync()->AbstractBootStrap#doBind()->doBind0()->AbstractChannel#pipeline.fireChannelActive()->AbstractNioChannel#selectionKey.interestOps(interestOps|readInterestOp)
: 注册 OP_READ 事件
上述启动流程中,1、2、3 是由咱们本人的线程执行的,即 mainThread,4、5、6 是由 Boss Thread 执行。相应时序图如下:
建设连贯
服务启动后便是建设连贯的过程了,相应过程及源码类如下:
NioEventLoop#run()->processSelectedKey()
NioEventLoop 中的 selector 轮询创立连贯事件(OP_ACCEPT)NioEventLoop#run()->processSelectedKey()->AbstractNioMessageChannel#read->NioServerSocketChannel#doReadMessages()->SocketUtil#accept(serverSocketChannel)
创立 socket channelNioEventLoop#run()->processSelectedKey()->AbstractNioMessageChannel#fireChannelRead->ServerBootstrap#ServerBootstrapAcceptor#channelRead-> childGroup.register(child)
从 worker group 中抉择一个 NioEventLoop 开始注册 socket channelNioEventLoop#run()->processSelectedKey()->AbstractNioMessageChannel#fireChannelRead->ServerBootstrap#ServerBootstrapAcceptor#channelRead-> childGroup.register(child)->AbstractChannel#register0(promise)-> AbstractNioChannel#javaChannel().register(eventLoop().unwrappedSelector(), 0, this)
将 socket channel 注册到抉择的 NioEventLoop 的 selectorNioEventLoop#run()->processSelectedKey()->AbstractNioMessageChannel#fireChannelRead->ServerBootstrap#ServerBootstrapAcceptor#channelRead-> childGroup.register(child)->AbstractChannel#pipeline.fireChannelActive()-> AbstractNioChannel#selectionKey.interestOps(interestOps | readInterestOp)
注册 OP_ACCEPT 事件
同样,上述流程中 1、2、3 的执行仍由 Boss Thread 执行,直到 4、5 由具体的 Work Thread 执行。
读写与业务解决
连贯建设结束后是具体的读写,以及业务解决逻辑。以 EchoServerHandler 为例,读取数据后会将数据流传进来供业务逻辑解决,此时的 EchoServerHandler 代表咱们的业务逻辑,而它的实现也非常简单,就是间接将数据写回去。咱们将这块看成一个整条,流程如下:
NioEventLoop#run()->processSelectedKey() NioEventLoop 中的 selector
轮询创立读取事件(OP_READ)NioEventLoop#run()->processSelectedKey()->AbstractNioByteChannel#read()
nioSocketChannel 开始读取数据NioEventLoop#run()->processSelectedKey()->AbstractNioByteChannel#read()->pipeline.fireChannelRead(byteBuf)
把读取到的数据流传进来供业务解决AbstractNioByteChannel#pipeline.fireChannelRead->EchoServerHandler#channelRead
在这个例子中即 EchoServerHandler 的执行EchoServerHandler#write->ChannelOutboundBuffer#addMessage
调用 write 办法EchoServerHandler#flush->ChannelOutboundBuffer#addFlush
调用 flush 筹备数据EchoServerHandler#flush->NioSocketChannel#doWrite
调用 flush 发送数据
在这个过程中读写数据都是由 Work Thread 执行的,然而业务解决能够由咱们自定义的线程池来解决,并且个别咱们也是这么做的,默认没有指定线程的状况下依然由 Work Thread 代为解决。
敞开连贯
服务处理完毕后,单个连贯的敞开是什么样的呢?
NioEventLoop#run()->processSelectedKey()
NioEventLoop 中的 selector 轮询创立读取事件(OP_READ),这里敞开连贯依然是读取事件NioEventLoop#run()->processSelectedKey()->AbstractNioByteChannel#read()->closeOnRead(pipeline)
当字节 <0 时开始执行敞开 nioSocketChannelNioEventLoop#run()->processSelectedKey()->AbstractNioByteChannel#read()->closeOnRead(pipeline)->AbstractChannel#close->AbstractNioChannel#doClose()
敞开 socketChannelNioEventLoop#run()->processSelectedKey()->AbstractNioByteChannel#read()->closeOnRead(pipeline)->AbstractChannel#close->outboundBuffer.failFlushed/close
清理音讯:不承受新信息,fail 掉所有 queue 中音讯NioEventLoop#run()->processSelectedKey()->AbstractNioByteChannel#read()->closeOnRead(pipeline)->AbstractChannel#close->fireChannelInactiveAndDeregister->AbstractNioChannel#doDeregister eventLoop().cancel(selectionKey())
敞开多路复用器的 key
时序图如下:
敞开服务
最初是敞开整个 Netty 服务:
NioEventLoop#run->closeAll()->selectionKey.cancel/channel.close
敞开 channel,勾销 selectionKeyNioEventLoop#run->confirmShutdown->cancelScheduledTasks
勾销定时工作NioEventLoop#cleanup->selector.close()
敞开 selector
时序图如下,为了好画将 NioEventLoop 拆成了 2 块:
至此,整个 Netty 的服务流程就完结了。
近期热文举荐:
1.600+ 道 Java 面试题及答案整顿 (2021 最新版)
2. 终于靠开源我的项目弄到 IntelliJ IDEA 激活码了,真香!
3. 阿里 Mock 工具正式开源,干掉市面上所有 Mock 工具!
4.Spring Cloud 2020.0.0 正式公布,全新颠覆性版本!
5.《Java 开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞 + 转发哦!