1. 程式人生 > >netty(十)原始碼分析之ByteBuf

netty(十)原始碼分析之ByteBuf

通過對netty的API的學習,可以更加遊刃有餘的使用netty的相關類庫
對原始碼的學習不僅能夠從原始碼層面掌握netty框架,方便日後的維護,拓展和定製而且可以起到觸類旁通的作用,拓展讀者的知識面,提升程式設計技能。

當我們進行資料傳輸的時候,往往需要使用到緩衝區,常用的緩衝區就是JDK提供的java.nio.Buffer,它的實現類如下所示:

實際上7中資料型別(Boolean除外)都有自己的緩衝區實現,對於NIO程式設計而言,我們主要使用的是ByteBuffer,從功能角度而言,ByteBuffer完全可以滿足NIO程式設計的需要,但是由於NIO程式設計的複雜性,ByteBuffer也有其侷限性,主要缺點如下:

(1)ByteBuffer長度固定,一旦分配完成,它的容量不能動態擴充套件和收縮,當需要編碼的POJO物件大於ByteBuffer容量時,會發生索引越界異常。
(2)ByteBuffer只有一個標識位置的指標position,讀寫的時候需要手工呼叫flip()和rewind()等,使用者必須小心謹慎地處理這些API,否則很容易導致程式處理失敗。
(3)ByteBuffer的API功能有限,一些高階和實用的特性它不支援,需要使用者自己程式設計實現。

為了彌補這些不足,Netty提供了自己的ByteBuffer實現————ByteBuf,下面我們一起學習ByteBuf的原理和主要功能。

ByteBuf的工作原理

不同ByteBuf實現類的工作原理不盡相同,我們從ByteBuf的設計原理出發,一起探尋Netty ByteBuf的設計理念。

首先,ByteBuf依然是個Byte陣列的緩衝區,它的基本功能應該與JDK的ByteBuffer一致,提供以下幾類基本功能。

  • 7中Java基礎型別,byte陣列,ByteBufer(ByteBuf)等的讀寫;
  • 緩衝區自身的copy和slice等;
  • 設定網路位元組序;
  • 構造緩衝區例項;
  • 操作位置指標等方法。

由於JDK的ByteBuffer已經提供了這些基礎能力的實現,因此,Netty ByteBuf的實現可以有兩種策略。

  • 參考JDK ByteBuffer的實現,增加額外的功能,解決原ByteBuffer的缺點;
  • 聚合JDK ByteBuffer,通過Facade模式對其進行包裝,可以減少自身的程式碼量,降低實現成本。

ByteBuf通過兩個位置指標來協助緩衝區的讀寫操作,讀操作使用readerIndex,寫操作使用writerIndex。
readerIndex和writerIndex的取值一開始都是0,隨著資料的寫入writerIndex會增加,讀取資料readerIndex增加,但是它不會超過writerIndex。在讀取之後,0~readerIndex就被視為discard的,呼叫discardReadBytes方法,可以釋放這部分空間,它的作用類似ByteBuffer的conpact方法。ReaderIndex和writerIndex之間的資料是可讀取的,等價於ByteBufer position和limit之間的資料。WriterIndex和capacity之間的空間是可寫的,等價於ByteBuffer limit和capacity之間的可用空間。

由於寫操作不修改readerIndex指標,讀操作不修改writerIndex指標,因此讀寫之間不再需要調整位置指標,這極大地簡化了緩衝區的讀寫操作,避免了由於遺漏或者不熟悉flip()操作導致的功能異常。
初始分配的ByteBuf如下圖所示:

寫入N個位元組後的ByteBuf如下圖所示:

讀取M(<N)個位元組之後的ByteBuf如下圖:

呼叫discardReadBytes操作之後的ByteBuf如下圖:

呼叫clear操作之後的ByteBuf如下圖:

ByteBuf對write操作進行了封裝,由ByteBuf的write操作負責進行剩餘可用空間的校驗。如果可用緩衝區不足,ByteBuf會自動進行動態擴充套件。對於使用者而言,不需要關心底層的校驗和擴充套件細節,只要不超過設定的最大緩衝區容量即可。當可用空間不足時,ByteBuf會幫助我們實現自動擴充套件,這極大地降低了ByteBuf的學習和使用成本,提升了開發效率。校驗和擴充套件的相關程式碼如下:

public ByteBuf writeByte(int value) {
        this.ensureWritable(1);
        this.setByte(this.writerIndex++, value);
        return this;
    }
通過原始碼分析,我們發現當進行write操作時,會對需要write的自己進行校驗。如果可寫的位元組數小於需要寫入的位元組數,並且需要寫入的位元組數小於可寫的最大位元組數(最大位元組數為自己建立buff設定的容量值),就會對緩衝區進行動態擴充套件。無論緩衝區是否進行了動態擴充套件,從功能角度看使用者並不感知,這樣就簡化了上層的應用。
public ByteBuf ensureWritable(int minWritableBytes) {
        if(minWritableBytes < 0) {
            throw new IllegalArgumentException(String.format("minWritableBytes: %d (expected: >= 0)", new Object[]{Integer.valueOf(minWritableBytes)}));
        } else if(minWritableBytes <= this.writableBytes()) {
            return this;
        } else if(minWritableBytes > this.maxCapacity - this.writerIndex) {
            throw new IndexOutOfBoundsException(String.format("writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s", new Object[]{Integer.valueOf(this.writerIndex), Integer.valueOf(minWritableBytes), Integer.valueOf(this.maxCapacity), this}));
        } else {
            int newCapacity = this.calculateNewCapacity(this.writerIndex + minWritableBytes);
            this.capacity(newCapacity);
            return this;
        }
    }

ByteBuf的功能介紹

ByteBuf常用API進行分類說明,講解它的主要功能,後面我們還會在給出重要API的一些典型用法。

1.順序讀操作(read)
ByteBuf的read操作類似於ByteBuffer的get操作,主要的API功能說夢如下表所示。


2.順序寫操作(write)
ByteBuf的write操作類似於ByteBuffer的put操作,主要的API功能如下表所示: