1. 程式人生 > 實用技巧 >一文了解Netty整體流程

一文了解Netty整體流程

本文基於版本 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 的服務流程就結束了。

特別推薦一個分享後端架構+演算法的優質內容,還沒關注的小夥伴,可以長按掃碼填加下方VX免費獲取: