乐趣区

关于java:一文搞懂-Netty-的整体流程还有谁不会

作者:fredalxin\
地址:https://fredal.xin/netty-process

本文基于版本 4.1.46,同时只形容类而不展现具体源码。

Netty 的整体流程

Netty 的整体流程相对来说还是比较复杂的,初学者往往会被绕晕。

所以这里总结了一下整体的流程,从而对 Netty 的整体服务流程有一个大抵的理解。从性能上,流程能够分为服务启动、建设连贯、读取数据、业务解决、发送数据、敞开连贯以及敞开服务。

整体流程如下所示 (图中没有蕴含敞开的局部):

服务启动

服务启动时,咱们以 example 代码中的 EchoServer 为例,启动的过程以及相应的源码类如下:

  1. EchoServer#new NioEventLoopGroup(1)->NioEventLoop#provider.openSelector() : 创立 selector
  2. EchoServer#b.bind(PORT).sync->AbstractBootStrap#doBind()->initAndRegister()-> channelFactory.newChannel() / init(channel) : 创立 serverSocketChannel 以及初始化
  3. EchoServer#b.bind(PORT).sync->AbstractBootStrap#doBind()->initAndRegister()-> config().group().register(channel):从 boss group 中抉择一个 NioEventLoop 开始注册 serverSocketChannel
  4. EchoServer#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 的 selector
  5. EchoServer#b.bind(PORT).sync()->AbstractBootStrap#doBind()->doBind0()->AbstractChannel#doBind(localAddress)->NioServerSocketChannel#javaChannel().bind(localAddress, config.getBacklog()) : 绑定地址端口开始启动
  6. 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 执行。相应时序图如下:

建设连贯

服务启动后便是建设连贯的过程了,相应过程及源码类如下:

  1. NioEventLoop#run()->processSelectedKey() NioEventLoop 中的 selector 轮询创立连贯事件(OP_ACCEPT)
  2. NioEventLoop#run()->processSelectedKey()->AbstractNioMessageChannel#read->NioServerSocketChannel#doReadMessages()->SocketUtil#accept(serverSocketChannel) 创立 socket channel
  3. NioEventLoop#run()->processSelectedKey()->AbstractNioMessageChannel#fireChannelRead->ServerBootstrap#ServerBootstrapAcceptor#channelRead-> childGroup.register(child) 从 worker group 中抉择一个 NioEventLoop 开始注册 socket channel
  4. NioEventLoop#run()->processSelectedKey()->AbstractNioMessageChannel#fireChannelRead->ServerBootstrap#ServerBootstrapAcceptor#channelRead-> childGroup.register(child)->AbstractChannel#register0(promise)-> AbstractNioChannel#javaChannel().register(eventLoop().unwrappedSelector(), 0, this) 将 socket channel 注册到抉择的 NioEventLoop 的 selector
  5. NioEventLoop#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 代表咱们的业务逻辑,而它的实现也非常简单,就是间接将数据写回去。咱们将这块看成一个整条,流程如下:

  1. NioEventLoop#run()->processSelectedKey() NioEventLoop 中的 selector 轮询创立读取事件(OP_READ)
  2. NioEventLoop#run()->processSelectedKey()->AbstractNioByteChannel#read() nioSocketChannel 开始读取数据
  3. NioEventLoop#run()->processSelectedKey()->AbstractNioByteChannel#read()->pipeline.fireChannelRead(byteBuf) 把读取到的数据流传进来供业务解决
  4. AbstractNioByteChannel#pipeline.fireChannelRead->EchoServerHandler#channelRead 在这个例子中即 EchoServerHandler 的执行
  5. EchoServerHandler#write->ChannelOutboundBuffer#addMessage 调用 write 办法
  6. EchoServerHandler#flush->ChannelOutboundBuffer#addFlush 调用 flush 筹备数据
  7. EchoServerHandler#flush->NioSocketChannel#doWrite 调用 flush 发送数据

在这个过程中读写数据都是由 Work Thread 执行的,然而业务解决能够由咱们自定义的线程池来解决,并且个别咱们也是这么做的,默认没有指定线程的状况下依然由 Work Thread 代为解决。

敞开连贯

服务处理完毕后,单个连贯的敞开是什么样的呢?

  1. NioEventLoop#run()->processSelectedKey() NioEventLoop 中的 selector 轮询创立读取事件(OP_READ),这里敞开连贯依然是读取事件
  2. NioEventLoop#run()->processSelectedKey()->AbstractNioByteChannel#read()->closeOnRead(pipeline) 当字节 <0 时开始执行敞开 nioSocketChannel
  3. NioEventLoop#run()->processSelectedKey()->AbstractNioByteChannel#read()->closeOnRead(pipeline)->AbstractChannel#close->AbstractNioChannel#doClose() 敞开 socketChannel
  4. NioEventLoop#run()->processSelectedKey()->AbstractNioByteChannel#read()->closeOnRead(pipeline)->AbstractChannel#close->outboundBuffer.failFlushed/close 清理音讯:不承受新信息,fail 掉所有 queue 中音讯
  5. NioEventLoop#run()->processSelectedKey()->AbstractNioByteChannel#read()->closeOnRead(pipeline)->AbstractChannel#close->fireChannelInactiveAndDeregister->AbstractNioChannel#doDeregister eventLoop().cancel(selectionKey()) 敞开多路复用器的 key

时序图如下:

敞开服务

最初是敞开整个 Netty 服务:

  1. NioEventLoop#run->closeAll()->selectionKey.cancel/channel.close 敞开 channel,勾销 selectionKey
  2. NioEventLoop#run->confirmShutdown->cancelScheduledTasks 勾销定时工作
  3. 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 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

退出移动版