天天看點

Netty|01 入門學習

1、Neetty 是由 JBOSS 提供的一個 Java 開源架構。Netty 提供異步的、基于事件驅動的網絡 應用程式架構,用以快速開發高性能、高可靠性的網絡 IO 程式。

2、Netty 是一個基于 NIO 的網絡程式設計架構,使用 Netty 可以幫助你快速、簡單的開發出一 個網絡應用,相當于簡化和流程化了 NIO 的開發過程。

3、作為目前最流行的 NIO 架構,Netty 在網際網路領域、大資料分布式計算領域、遊戲行業、 通信行業等獲得了廣泛的應用,知名的 Elasticsearch 、Dubbo 架構内部都采用了 Netty。

單線程模型:

伺服器端用一個線程通過多路複用搞定所有的 IO 操作(包括連接配接,讀、寫等),編碼簡單,清晰明了, 但是如果用戶端連接配接數量較多, 将無法支撐,NIO程式設計技術就是典型的單線程模型!

線程池模型

伺服器端采用一個線程專門處理用戶端連接配接請求,采用一個線程池負責 IO 操作。在絕大多數場景下,該模型都能滿足使用。

Netty模型

比較類似于上面的線程池模型,Netty 抽象出兩組線程池,BossGroup 專門負責接收客 戶端連接配接,WorkerGroup 專門負責網絡讀寫操作。NioEventLoop 表示一個不斷循環執行處理 任務的線程,每個 NioEventLoop 都有一個 selector,用于監聽綁定在其上的 socket 網絡通道。 NioEventLoop 内部采用串行化設計,從消息的讀取->解碼->處理->編碼->發送,始終由 IO 線 程 NioEventLoop 負責。

一個 NioEventLoopGroup 下包含多個 NioEventLoop

每個 NioEventLoop 中包含有一個 Selector,一個 taskQueue

每個 NioEventLoop 的 Selector 上可以注冊監聽多個 NioChannel

每個 NioChannel 隻會綁定在唯一的 NioEventLoop 上

每個 NioChannel 都綁定有一個自己的 ChannelPipeline

Futrue和CallBack

Netty 的異步模型是建立在 future 和 callback 的之上的。 callback 大家都比較熟悉了,這裡重點說說 Future,它的核心思想是:假設一個方法 fun,計算過程可能非常耗時,等待 fun 傳回顯然不合适。那麼可以在調用 fun 的時候,立馬傳回一個 Future,後續可以通過 Future 去監控方法 fun 的處理過程。

在使用 Netty 進行程式設計時,攔截操作和轉換出入站資料隻需要您提供 callback 或利用 future 即可。這使得鍊式操作簡單、高效, 并有利于編寫可重用的、通用的代碼。Netty 框 架的目标就是讓你的業務邏輯從網絡基礎應用編碼中分離出來、解脫出來。

Handler

首先需要确立的一個觀念就是,Netty是一個基于鍊式的開發的模式,這裡可以了解為我們可以自定義許多的Handler來放在這個處理消息的鍊子(pipeline)上,而這些Handler就是我們在實際的開發中需要編寫的。

大體分為:channelHandlerAdapter、channelOutboundHandler和channelInboundHandler

接口隻定義了這幾個通用的方法,其他交給實作類來拓展

方法的何時被調用我寫在了注釋裡。

再來看看我們最常用到的實作類ChannelInboundHandler的内部方法,方法使用說明我寫在了内部注釋中

ChannelInboundHandlerAdapter的每個方法的預設實作都是通過ChannelHandlerContext将IO事件或接收到的資料,傳給所在的ChannelPipeline的下一個ChannelInboundHandler:

1、業務處理邏輯處理:使用者可以通過拓展ChannelInboundHandlerAdapter,重寫相應的方法來,生成新的子類的方式來定義業務需要的處理邏輯,Netty預設針對特定功能的處理,提供了一些 ChannelInboundHandler的實作類

對于從Channel讀入的資料,在調用channelRead方法處理時,預設實作也是傳給下一個ChannelInboundHandler處理,不會銷毀該資料對象,釋放掉該資料所占用的空間的,如果不需要繼續往下傳輸,則可以調用ReferenceCountUtil.release(msg)手動釋放掉。

3、但是對于實作類SimpleChannelInboundHandler來說,重寫了ChannelInboundHandler的channelRead方法,預設會在最後釋放掉msg的資源,是以是無法儲存msg的引用的。

而SimpleChannelInboundHandler還新增了一個方法channelRead0(在netty 5.0之後channelRead0方法名稱變成了messageReceived),這個方法的特殊在于,它是隻會在msg通過了編碼器解碼器之後才會執行的方法,參數本身會幫你轉換為類上寫的泛型,而這個泛型的資料類型就是在pipeline鍊中增加的編碼解碼器中對應的類型。

值得注意的是,如果沒有進行對應的編碼解碼就直接重寫channelRead0方法的話,netty既不會處理傳來的msg,也不會報錯異常,這是一個很坑的點。

資料的保留問題:如果使用者在實作channelRead0方法自定義資料處理邏輯時,需要将該資料傳給下一個ChannelInboundHandler,則需要調用ReferenceCountUtil.retain(msg)方法,原理是将msg的引用計數加1,因為ReferenceCountUtil.release(msg)是将msg的引用計數減1,同時當引用計數變成0時,釋放該資料:(參考StringHandler)

對于ChannelOutboundHandler需要注意的點:

1、與ChannelInboundHandler類似,方法預設實作也是通過ChannelHandlerContext将寫出的資料,交給下一個ChannelOutboundHandler處理。

可以看出:與ChannelInboundHandlerAdapter的作用類似,方法預設實作也是通過ChannelHandlerContext将寫出的資料,交給下一個ChannelOutboundHandler處理。

由此可知:ChannelDuplexHandler具備ChannelInboundHandler和ChannelInboundHandler的功能,可以實作讀與寫的功能,處理完資料後也是傳遞給下一個ChannelDuplexHandler處理

1、ChannelInboundHandler和ChannelOutboundHandler的關系:

ChannelInboundHandler用于定義對讀入IO事件的處理,而ChannelOutboundHandler用于定義寫出IO事件的處理。

2、對于ChannelHandler裡面的方法的執行順序:将會在下一篇部落格中單獨做出解釋

3、Netty在handler子子產品,針對不同的功能,包括流量控制,資料flush,ip過濾,日志,ssl,大資料流處理,逾時心跳檢測,擁塞控制,提供了相應的ChannelHandler實作類。---引用

這是事件處理器上下文對象,Pipeline 鍊中的實際處理節點。每個處理節點 ChannelHandlerContext 中 包 含 一 個 具 體 的 事 件 處 理 器 ChannelHandler ,同 時 ChannelHandlerContext中也綁定了對應的pipeline和Channel的資訊,友善對ChannelHandler 進行調用。常用方法如下所示:

 ChannelFutureclose(),關閉通道

 ChannelOutboundInvokerflush(),重新整理

 ChannelFuture writeAndFlush(Object msg) ,将 數 據 寫 到 ChannelPipeline 中 當 前 ChannelHandler 的下一個 ChannelHandler 開始處理(出站)

Netty 在建立 Channel 執行個體後,一般都需要設定 ChannelOption 參數

ChannelOption 是 Socket 的标準參數,而非 Netty 獨創的。常用的參數

ChannelOption.SO_BACKLOG 對應 TCP/IP 協定 listen 函數中的 backlog 參數,用來初始化伺服器可連接配接隊列大小。服務端處理用戶端連接配接請求是順序處理的,是以同一時間隻能處理一個用戶端連接配接。多個客戶 端來的時候,服務端将不能處理的用戶端連接配接請求放在隊列中等待處理,backlog 參數指定 了隊列的大小。

ChannelOption.SO_KEEPALIVE ,一直保持連接配接活動狀态。

表示 Channel 中異步 I/O 操作的結果,在 Netty 中所有的 I/O 操作都是異步的,I/O 的調 用會直接傳回,調用者并不能立刻獲得結果,但是可以通過 ChannelFuture 來擷取 I/O 操作 的處理狀态。

常用方法如下所示:

1.Channelchannel(),傳回目前正在進行 IO 操作的通道

2.ChannelFuturesync(),等待異步操作執行完畢

EventLoopGroup 是一組 EventLoop 的抽象,Netty 為了更好的利用多核 CPU 資源,一般 會有多個 EventLoop 同時工作,每個 EventLoop 維護着一個 Selector 執行個體。

EventLoopGroup 提供 next 接口,可以從組裡面按照一定規則擷取其中一個 EventLoop 來處理任務。在 Netty 伺服器端程式設計中,我們一般都需要提供兩個 EventLoopGroup,如:BossEventLoopGroup 和 WorkerEventLoopGroup。

通常一個服務端口即一個ServerSocketChannel對應一個Selector和一個EventLoop線程。BossEventLoop 負責接收用戶端的連接配接并将 SocketChannel 交給 WorkerEventLoopGroup 來進 行 IO 處理

BossEventLoopGroup 通常是一個單線程的 EventLoop,EventLoop 維護着一個注冊了 ServerSocketChannel的Selector執行個體, BossEventLoop不斷輪詢Selector将連接配接事件分離出來, 通常是 OP_ACCEPT 事件,然後将接收到的 SocketChannel 交給 WorkerEventLoopGroup, WorkerEventLoopGroup 會由 next 選擇其中一個 EventLoopGroup 來将這個 SocketChannel 注 冊到其維護的 Selector 并對其後續的 IO 事件進行處理。常用方法如下所示:

1.publicNioEventLoopGroup(),構造方法

2.publicFuture<?>shutdownGracefully(),斷開連接配接,關閉線程

ServerBootstrap 是 Netty 中的伺服器端啟動助手,通過它可以完成伺服器端的各種配置;Bootstrap 是 Netty 中的用戶端啟動助手,通過它可以完成用戶端的各種配置。常用方法如下 所示:

1.publicServerBootstrapgroup(EventLoopGroup parentGroup,EventLoopGroupchildGroup), 該方法用于伺服器端,用來設定兩個 EventLoop

2.publicBgroup(EventLoopGroupgroup) ,該方法用于用戶端,用來設定一個 EventLoop

3.publicBchannel(Class<?extendsC>channelClass),該方法用來設定一個伺服器端的通道 實作

4.publicBoption(ChannelOptionoption,Tvalue),用來給 ServerChannel 添加配置

5.publicServerBootstrapchildOption(ChannelOptionchildOption,Tvalue),用來給接 收到的通道添加配置

6.public ServerBootstrapchildHandler(ChannelHandler childHandler),該方法用來設定業務 處理類(自定義的 handler)

7.publicChannelFuturebind(intinetPort) ,該方法用于伺服器端,用來設定占用的端口号

8.publicChannelFutureconnect(StringinetHost,intinetPort) ,該方法用于用戶端,用來連 接伺服器端

這是 Netty 提供的一個專門用來操作緩沖區的工具類,常用方法如下

publicstaticByteBufcopiedBuffer(CharSequencestring,Charsetcharset),通過給定的資料 和字元編碼傳回一個 ByteBuf 對象(類似于 NIO 中的 ByteBuffer 對象)

我們在編寫網絡應用程式的時候需要注意codec(編解碼器),因為資料在網絡中傳輸的 都是二進制位元組碼資料,而我們拿到的目标資料往往不是位元組碼資料。是以在發送資料時就 需要編碼,接收資料時就需要解碼。

codec 的組成部分有兩個:decoder(解碼器)和 encoder(編碼器)。

encoder 負責把業務數 據轉換成位元組碼資料,decoder 負責把位元組碼資料轉換成業務資料。

Java 的序列化技術就可以作為 codec 去使用,

但是它的硬傷太多:

無法跨語言,這應該是 Java 序列化最緻命的問題了。

序列化後的體積太大,是二進制編碼的 5 倍多。

序列化性能太低。

StringEncoder,對字元串資料進行編碼

ObjectEncoder,對 Java 對象進行編碼

Netty 本身自帶的 ObjectDecoder 和 ObjectEncoder 可以用來實作 POJO 對象或各種業務 對象的編碼和解碼,但其内部使用的仍是 Java 序列化技術,是以我們不建議使用。是以對 于 POJO 對象或各種業務對象要實作編碼和解碼,我們需要更高效更強的技術,這時就需要使用一些插件了。推薦使用Google 的 Protobuf