作者: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 正式發佈,全新顛覆性版本!
覺得不錯,別忘了隨手點贊+轉發哦!