netty系列之:netty架構概述


簡介

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/

最通俗的解讀,最深刻的干貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程序那些事」,懂技術,更懂你!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM