一文搞懂 Netty 的總體流程,還有誰不會?

做者:fredalxin\
地址:https://fredal.xin/netty-processjava

本文基於版本 4.1.46,同時只描述類而不展現具體源碼。面試

Netty 的總體流程

Netty 的總體流程相對來講仍是比較複雜的,初學者每每會被繞暈。spring

因此這裏總結了一下總體的流程,從而對 Netty 的總體服務流程有一個大體的瞭解。從功能上,流程能夠分爲服務啓動、創建鏈接、讀取數據、業務處理、發送數據、關閉鏈接以及關閉服務。promise

總體流程以下所示(圖中沒有包含關閉的部分):intellij-idea

服務啓動

服務啓動時,咱們以 example 代碼中的 EchoServer 爲例,啓動的過程以及相應的源碼類以下:app

  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 事件

上述啓動流程中,一、二、3 是由咱們本身的線程執行的,即 mainThread,四、五、6 是由 Boss Thread 執行。相應時序圖以下:socket

創建鏈接

服務啓動後即是創建鏈接的過程了,相應過程及源碼類以下:ide

  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 事件

一樣,上述流程中 一、二、3 的執行仍由 Boss Thread 執行,直到 四、5 由具體的 Work Thread 執行。工具

讀寫與業務處理

鏈接創建完畢後是具體的讀寫,以及業務處理邏輯。以 EchoServerHandler 爲例,讀取數據後會將數據傳播出去供業務邏輯處理,此時的 EchoServerHandler 表明咱們的業務邏輯,而它的實現也很是簡單,就是直接將數據寫回去。咱們將這塊當作一個整條,流程以下:oop

  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開發手冊(嵩山版)》最新發布,速速下載!

以爲不錯,別忘了隨手點贊+轉發哦!

相關文章
相關標籤/搜索