簡介
Netty為什麼這麼優秀,它在JDK本身的NIO基礎上又做了什麼改進呢?它的架構和工作流程如何呢?請走進今天的netty系列文章之:netty架構概述。
netty架構圖
netty的主要作用就是提供一個簡單的NIO架構可以和上層的各種協定相結合,最終實作高性能的伺服器。下面是netty官網提供的架構圖:
從上圖可以看到netty的核心主要分成三部分,分别是可擴充的event model、統一的API、和強大的Byte Buffer。這三個特性是netty的制勝法寶。
下面會從這幾個方面對netty的特性進行詳細說明,務必讓讀者了解到netty的優秀之處。
豐富的Buffer資料機構
首先從最底層的Buffer資料結構開始,netty提供了一個io.netty.buffer的包,該包裡面定義了各種類型的ByteBuf和其衍生的類型。
netty Buffer的基礎是ByteBuf類,這是一個抽象類,其他的Buffer類基本上都是由該類衍生而得的,這個類也定義了netty整體Buffer的基調。
netty重寫ByteBuf其目的是讓最底層的ByteBuf比JDK自帶的更快更适合擴充。具體而言,netty的ByteBuf要比JDK中的ByteBuffer要快,同時,擴充也更加容易,大家可以根據需要Buf進行自定義。另外netty有一些内置的複合緩沖區類型,是以可以實作透明的零拷貝。對于動态緩沖區類型來說,可以和StringBuffer一樣按需擴充,非常好用。
零拷貝
什麼是零拷貝呢?零拷貝的意思是在需要拷貝的時候不做拷貝。我們知道資料在使用底層協定進行傳輸的過程中是會被封裝成為一個個的包進行傳輸。當傳輸的資料過大,一個包放不下的時候,還需要對資料進行拆分,目的方在接收到資料之後,需要對收到的資料進行組裝,一般情況下這個組裝的操作是對資料的拷貝,将拆分過後的對象拷貝到一個長的資料空間中。
比如下面的例子所示,将底層的TCP包組合稱為頂層的HTTP包,但是并沒有進行拷貝:
具體怎麼拷貝呢?在上一篇文章中,我們知道netty提供了一個工具類方法Unpooled,這個工具類中有很多wrapped開頭的方法,我們舉幾個例子:
public static ByteBuf wrappedBuffer(byte[]... arrays) {
return wrappedBuffer(arrays.length, arrays);
}
public static ByteBuf wrappedBuffer(ByteBuf... buffers) {
return wrappedBuffer(buffers.length, buffers);
}
public static ByteBuf wrappedBuffer(ByteBuffer... buffers) {
return wrappedBuffer(buffers.length, buffers);
}
上面三個方法分别是封裝byte數組、封裝ByteBuf和封裝ByteBuffer,這些方法都是零拷貝。大家可以在實際的項目中根據實際情況,自行選用。
統一的API
一般來說,在傳統的JDK的IO API中,根據傳輸類型或者協定的不同,使用的API也是不同的。我們需要對不同的傳輸方式開發不同的應用程式,不能做到統一。這樣的結果就是無法平滑的遷移,并且在程式擴充的時候需要進行額外的處理。
什麼是傳輸方式呢?這裡是指以什麼樣的方式來實作IO,比如傳統的阻塞型IO,我們可以稱之為OIO,java的new IO可以稱之為NIO,異步IO可以稱之為AIO等等。
并且JDK中的傳統IO和NIO是割裂的,如果在最開始你使用的是傳統IO,那麼當你的客戶數目增長到一定的程度準備切換到NIO的時候,就會發現切換起來異常複雜,因為他們是割裂的。
為了解決這個問題,netty提供了一個統一的類Channel來提供統一的API。
先看下Channel中定義的方法:
從上圖我們可以看到使用Channel可以判斷channel目前的狀态,可以對其進行參數配置,可以對其進行I/O操作,還有和channel相關的ChannelPipeline用來處理channel關聯的IO請求和事件。
使用Channel就可以對NIO的TCP/IP,OIO的TCP/IP,OIO的UDP/IP和本地傳輸都能提供很好的支援。
傳輸方式的切換,隻需要進行很小的成本替換。
當然,如果你對現有的實作都不滿意的話,還可以對核心API進行自定義擴充。
事件驅動
netty是一個事件驅動的架構,事件驅動架構的基礎就是事件模型,Netty專門為IO定義了一個非常有效的事件模型。可以在不破壞現有代碼的情況下實作自己的事件類型。netty中自定義的事件類型通過嚴格的類型層次結構跟其他事件類型區分開來,是以可擴充性很強。
netty中的事件驅動是由ChannelEvent、ChannelHandler和ChannelPipeline共同作用的結果。其中ChannelEvent表示發生的事件,ChannelHandler定義了如何對事件進行處理,而ChannelPipeline類似一個攔截器,可以讓使用者自行對定義好的ChannelHandler進行控制,進而達到控制事件處理的結果。
我們看一個簡單的自定義Handler:
public class MyHandler extends SimpleChannelInboundHandler<Object> {
@Override
public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
// 對消息進行處理
ByteBuf in = (ByteBuf) msg;
try {
log.info("收到消息:{}",in.toString(io.netty.util.CharsetUtil.US_ASCII));
}finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
//異常處理
cause.printStackTrace();
ctx.close();
}
}
上面的例子中,我們定義了如何對接收到的消息和異常進行處理。在後續的文章中我們會詳細對ChannelEvent、ChannelHandler和ChannelPipeline之間的互動使用進行介紹。
其他優秀的特性
除了上面提到的三大核心特性之外,netty還有其他幾個優點,友善程式員的開發工作。
比如對SSL / TLS的支援,對HTTP協定的實作,對WebSockets 實作和Google Protocol Buffers的實作等等,表明netty在各個方面多個場景都有很強的應用能力。
總結
netty是由三個核心元件構成:緩沖區、通道和事件模型,通過了解這三個核心元件是如何互相工作的,那麼再去了解建立在netty之上的進階功能就不難了。
本文的例子可以參考:
learn-netty4本文已收錄于 http://www.flydean.com/03-netty-architecture/最通俗的解讀,最深刻的幹貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!
歡迎關注我的公衆号:「程式那些事」,懂技術,更懂你!