1. 程式人生 > 程式設計 >Dubbo原始碼解析(十一)遠端通訊——Buffer

Dubbo原始碼解析(十一)遠端通訊——Buffer

遠端通訊——Buffer

目標:介紹Buffer的相關實現邏輯、介紹dubbo-remoting-api中的buffer包內的原始碼解析。

前言

快取區在NIO框架中非常重要,它作為位元組容器,每個NIO框架都有自己的相應的設計實現。比如Java NIO有ByteBuffer的設計,Mina有IoBuffer的設計,Netty4有ByteBuf的設計。 那麼在本文講到的內容是dubbo對於緩衝區做的一些介面定義,並且做了不同的框架實現緩衝區公共的邏輯。下面是本文要講到的類圖:

buffer類圖

接下來我就按照類圖上的一個一個分析,忽略test類。

原始碼分析

(一)ChannelBuffer

該介面繼承了Comparable介面,該介面是通道快取介面,是位元組容器,在netty中也有通道快取的設計,也就是io.netty.buffer.ByteBuf,該介面的方法定義和設計跟ByteBuf幾乎一樣,連註釋都一樣,所以我就不再細說了。

(二)AbstractChannelBuffer

該類實現了ChannelBuffer介面,是通道快取的抽象類,它實現了ChannelBuffer所有方法,但是它實現的方法都是需要被重寫的方法,具體的實現都是需要子類來實現。現在我們來惡補一下這個通道快取的原理,當然這個原理跟netty的ByteBuf原理是分不開的。AbstractChannelBuffer維護了兩個索引,一個用於讀取,另一個用於寫入當你從通道快取中讀取時,readerIndex將會被遞增已經被讀取的位元組數,同樣的當你寫入的時候writerIndex也會被遞增。

/**
 * 讀索引
 */
private int readerIndex;

/**
 * 寫索引
 */
private int writerIndex; /** * 標記讀索引 */ private int markedReaderIndex; /** * 標記寫索引 */ private int markedWriterIndex; 複製程式碼

可以看到該類有四個屬性,讀索引和寫索引的作用就是我上述介紹的,讀索引和寫索引的起始位置都為索引位置0。而標記讀索引和標記寫索引是為了做備份回滾,當對緩衝區進行讀寫操作時,可能需要對之前的操作進行回滾,我們就需要將當前的讀寫索引備份到相應的標記索引中。

該類的其他方法都是利用四個屬性來操作,無非就是檢測是否有資料可讀或者還是否有空間可寫等方法,做一些前置條件的校驗以及索引的設定,具體的實現都是需要子類來實現,所以我就不貼程式碼,因為邏輯比較簡單。

(三)DynamicChannelBuffer

該類繼承了AbstractChannelBuffer類,該類是動態的通道快取區類,也就是該類是從ChannelBufferFactory工廠中動態的生成緩衝區,預設使用的工廠是HeapChannelBufferFactory。

1.屬性和構造方法

/**
 * 通道快取區工廠
 */
private final ChannelBufferFactory factory;

/**
 * 通道快取區
 */
private ChannelBuffer buffer;

public DynamicChannelBuffer(int estimatedLength) {
    // 預設是HeapChannelBufferFactory
    this(estimatedLength,HeapChannelBufferFactory.getInstance());
}

public DynamicChannelBuffer(int estimatedLength,ChannelBufferFactory factory) {
    // 如果預計長度小於0 則丟擲異常
    if (estimatedLength < 0) {
        throw new IllegalArgumentException("estimatedLength: " + estimatedLength);
    }
    // 如果工廠為空,則丟擲空指標異常
    if (factory == null) {
        throw new NullPointerException("factory");
    }
    // 設定工廠
    this.factory = factory;
    // 建立快取區
    buffer = factory.getBuffer(estimatedLength);
}
複製程式碼

可以看到,該類有兩個屬性,所有的實現方法都是呼叫了buffer的方法,不過該buffer產生是通過工廠動態生成的。並且從構造方法來看,預設使用HeapChannelBufferFactory。

2.ensureWritableBytes

@Override
public void ensureWritableBytes(int minWritableBytes) {
    // 如果最小寫入的位元組數不大於可寫的位元組數,則結束
    if (minWritableBytes <= writableBytes()) {
        return;
    }

    // 新增容量
    int newCapacity;
    // 此緩衝區可包含的位元組數等於0。
    if (capacity() == 0) {
        // 新增容量設定為1
        newCapacity = 1;
    } else {
        // 新增容量設定為緩衝區可包含的位元組數
        newCapacity = capacity();
    }
    // 最小新增容量 = 當前的寫索引+最小寫入的位元組數
    int minNewCapacity = writerIndex() + minWritableBytes;
    // 當新增容量比最小新增容量小
    while (newCapacity < minNewCapacity) {
        // 新增容量左移1位,也就是加倍
        newCapacity <<= 1;
    }

    // 通過工廠建立該容量大小當緩衝區
    ChannelBuffer newBuffer = factory().getBuffer(newCapacity);
    // 從buffer中讀取資料到newBuffer中
    newBuffer.writeBytes(buffer,0,writerIndex());
    // 替換原來到緩衝區
    buffer = newBuffer;
}
複製程式碼

該方法是確保陣列有可寫的容量,該方法是重寫了父類的方法,通過傳入一個最小寫入的位元組數,來對緩衝區進行擴容,可以看到,當現有的緩衝區不夠大的時候,會對緩衝區進行加倍對擴容,直到buffer的大小大於傳入的最小可寫位元組數。

3.copy

@Override
public ChannelBuffer copy(int index,int length) {
    // 建立緩衝區,預計長度最小為64,或者更大
    DynamicChannelBuffer copiedBuffer = new DynamicChannelBuffer(Math.max(length,64),factory());
    // 複製資料
    copiedBuffer.buffer = buffer.copy(index,length);
    // 設定索引,讀索引設定為0,寫索引設定為copy的資料長度
    copiedBuffer.setIndex(0,length);
    // 返回快取區
    return copiedBuffer;
}
複製程式碼

該方法是複製資料,在建立緩衝區的時候,預計長度最小是64,,然後重新設定讀索引寫索引。

其他方法都呼叫了buffer的方法或者呼叫了父類的方法,所以不再這裡多說。

(四)ByteBufferBackedChannelBuffer

該方法繼承AbstractChannelBuffer,該類是基於 Java NIO中的ByteBuffer來實現相關的讀寫資料等操作。

/**
 * ByteBuffer例項
 */
private final ByteBuffer buffer;

/**
 * 容量
 */
private final int capacity;

public ByteBufferBackedChannelBuffer(ByteBuffer buffer) {
    if (buffer == null) {
        throw new NullPointerException("buffer");
    }

    // 建立一個新的位元組緩衝區,新緩衝區的大小將是此緩衝區的剩餘容量
    this.buffer = buffer.slice();
    // 返回buffer的剩餘容量
    capacity = buffer.remaining();
    // 設定寫索引
    writerIndex(capacity);
}
複製程式碼

上述就是該類的屬性和建構函式,可以看到它有一個ByteBuffer型別的例項,並且capacity是buffer的剩餘容量。

還有其他的方法比如getByte方法是從buffer中讀取資料方法,setBytes方法是把資料寫入buffer,它們都有很多過載方法,為就不一一講解了,它們都是呼叫了ByteBuffer中的一些方法,如果對於Java NIO中的ByteBuffer方法不是很熟悉的朋友,需要先了解一下Java NIO中的ByteBuffer。

(五)HeapChannelBuffer

該方法繼承了AbstractChannelBuffer,該類中buffer是基於位元組陣列實現

/**
 * The underlying heap byte array that this buffer is wrapping.
 * 此緩衝區包裝的基礎堆位元組陣列。
 */
protected final byte[] array;

/**
 * Creates a new heap buffer with a newly allocated byte array.
 * 使用新分配的位元組陣列建立新的堆緩衝區。
 *
 * @param length the length of the new byte array
 */
public HeapChannelBuffer(int length) {
    this(new byte[length],0);
}

/**
 * Creates a new heap buffer with an existing byte array.
 * 使用現有位元組陣列建立新的堆緩衝區。
 *
 * @param array the byte array to wrap
 */
public HeapChannelBuffer(byte[] array) {
    this(array,array.length);
}

/**
 * Creates a new heap buffer with an existing byte array.
 * 使用現有位元組陣列建立新的堆緩衝區。
 *
 * @param array       the byte array to wrap
 * @param readerIndex the initial reader index of this buffer
 * @param writerIndex the initial writer index of this buffer
 */
protected HeapChannelBuffer(byte[] array,int readerIndex,int writerIndex) {
    if (array == null) {
        throw new NullPointerException("array");
    }
    this.array = array;
    setIndex(readerIndex,writerIndex);
}
複製程式碼

該類有好幾個建構函式,都是基於位元組陣列的,也就是在該類中包裝了一個位元組陣列,把建構函式傳入的位元組陣列傳入到該屬性中。其他方法邏輯比較簡單。

(六)ChannelBufferFactory

public interface ChannelBufferFactory {

    /**
     * 獲得緩衝區例項
     * @param capacity
     * @return
     */
    ChannelBuffer getBuffer(int capacity);

    ChannelBuffer getBuffer(byte[] array,int offset,int length);

    ChannelBuffer getBuffer(ByteBuffer nioBuffer);

}
複製程式碼

該介面是通道緩衝區工廠,其中就只定義了獲得通道緩衝區的方法,比較好理解,它有兩個實現類,我後續會講到。

(七)HeapChannelBufferFactory

該類實現了ChannelBufferFactory,該類就是基於位元組陣列來建立緩衝區的工廠。

public class HeapChannelBufferFactory implements ChannelBufferFactory {

    /**
     * 單例
     */
    private static final HeapChannelBufferFactory INSTANCE = new HeapChannelBufferFactory();

    public HeapChannelBufferFactory() {
        super();
    }

    public static ChannelBufferFactory getInstance() {
        return INSTANCE;
    }

    @Override
    public ChannelBuffer getBuffer(int capacity) {
        // 建立一個capacity容量的緩衝區
        return ChannelBuffers.buffer(capacity);
    }

    @Override
    public ChannelBuffer getBuffer(byte[] array,int length) {
        return ChannelBuffers.wrappedBuffer(array,offset,length);
    }

    @Override
    public ChannelBuffer getBuffer(ByteBuffer nioBuffer) {
        // 判斷該緩衝區是否有位元組陣列支援
        if (nioBuffer.hasArray()) {
            // 使用
            return ChannelBuffers.wrappedBuffer(nioBuffer);
        }

        // 建立一個nioBuffer剩餘容量的緩衝區
        ChannelBuffer buf = getBuffer(nioBuffer.remaining());
        // 記錄下nioBuffer的位置
        int pos = nioBuffer.position();
        // 寫入資料到buf
        buf.writeBytes(nioBuffer);
        // 把nioBuffer的位置重置到pos
        nioBuffer.position(pos);
        return buf;
    }

}
複製程式碼

該類利用了單例模式,其中的方法比較簡單,就是呼叫了ChannelBuffers中的方法,呼叫的方法實際上還是使用了HeapChannelBuffer中建立緩衝區的方法。

(八)DirectChannelBufferFactory

該類實現了ChannelBufferFactory介面,是直接緩衝區工廠,用來建立直接緩衝區。

public class DirectChannelBufferFactory implements ChannelBufferFactory {

    /**
     * 單例
     */
    private static final DirectChannelBufferFactory INSTANCE = new DirectChannelBufferFactory();

    public DirectChannelBufferFactory() {
        super();
    }

    public static ChannelBufferFactory getInstance() {
        return INSTANCE;
    }

    @Override
    public ChannelBuffer getBuffer(int capacity) {
        if (capacity < 0) {
            throw new IllegalArgumentException("capacity: " + capacity);
        }
        if (capacity == 0) {
            return ChannelBuffers.EMPTY_BUFFER;
        }
        // 生成直接緩衝區
        return ChannelBuffers.directBuffer(capacity);
    }

    @Override
    public ChannelBuffer getBuffer(byte[] array,int length) {
        if (array == null) {
            throw new NullPointerException("array");
        }
        if (offset < 0) {
            throw new IndexOutOfBoundsException("offset: " + offset);
        }
        if (length == 0) {
            return ChannelBuffers.EMPTY_BUFFER;
        }
        if (offset + length > array.length) {
            throw new IndexOutOfBoundsException("length: " + length);
        }

        ChannelBuffer buf = getBuffer(length);
        buf.writeBytes(array,length);
        return buf;
    }

    @Override
    public ChannelBuffer getBuffer(ByteBuffer nioBuffer) {
        // 如果nioBuffer不是隻讀,並且它是直接緩衝區
        if (!nioBuffer.isReadOnly() && nioBuffer.isDirect()) {
            // 建立一個緩衝區
            return ChannelBuffers.wrappedBuffer(nioBuffer);
        }

        // 建立一個nioBuffer剩餘容量的緩衝區
        ChannelBuffer buf = getBuffer(nioBuffer.remaining());
        // 記錄下nioBuffer的位置
        int pos = nioBuffer.position();
        // 寫入資料到buf
        buf.writeBytes(nioBuffer);
        // 把nioBuffer的位置重置到pos
        nioBuffer.position(pos);
        return buf;
    }
複製程式碼

該類中的實現方式與HeapChannelBufferFactory中的實現方式差不多,唯一的區別就是它建立的是一個直接緩衝區。

(九)ChannelBuffers

該類是緩衝區的工具類,提供建立、比較 ChannelBuffer 等公用方法。我在這裡舉兩個方法來講:

public static ChannelBuffer wrappedBuffer(ByteBuffer buffer) {
    // 如果緩衝區沒有剩餘容量
    if (!buffer.hasRemaining()) {
        return EMPTY_BUFFER;
    }
    // 如果是位元組陣列生成的緩衝區
    if (buffer.hasArray()) {
        // 使用buffer的位元組陣列生成一個新的緩衝區
        return wrappedBuffer(buffer.array(),buffer.arrayOffset() + buffer.position(),buffer.remaining());
    } else {
        // 基於ByteBuffer建立一個緩衝區(利用buffer的剩餘容量建立)
        return new ByteBufferBackedChannelBuffer(buffer);
    }
}
複製程式碼

該方法通過buffer來建立一個新的緩衝區。可以看出來呼叫的就是上述生成緩衝區的三個類中的方法,ChannelBuffers中很多方法都是這樣去實現的,邏輯比較簡單。

public static boolean equals(ChannelBuffer bufferA,ChannelBuffer bufferB) {
    // 獲得bufferA的可讀資料
    final int aLen = bufferA.readableBytes();
    // 如果兩個緩衝區的可讀資料大小不一樣,則不是同一個
    if (aLen != bufferB.readableBytes()) {
        return false;
    }

    final int byteCount = aLen & 7;

    // 獲得兩個比較的緩衝區的讀索引
    int aIndex = bufferA.readerIndex();
    int bIndex = bufferB.readerIndex();

    // 最多比較緩衝區中的7個資料
    for (int i = byteCount; i > 0; i--) {
        // 一旦有一個資料不一樣,則不是同一個
        if (bufferA.getByte(aIndex) != bufferB.getByte(bIndex)) {
            return false;
        }
        aIndex++;
        bIndex++;
    }

    return true;
}
複製程式碼

該方法就是比較兩個緩衝區是否為同一個,重寫了equals。

(十)ChannelBufferOutputStream

該類繼承了OutputStream

1.屬性和構造方法

/**
 * 緩衝區
 */
private final ChannelBuffer buffer;
/**
 * 記錄開始寫入的索引
 */
private final int startIndex;

public ChannelBufferOutputStream(ChannelBuffer buffer) {
    if (buffer == null) {
        throw new NullPointerException("buffer");
    }
    this.buffer = buffer;
    // 把開始寫入資料的索引記錄下來
    startIndex = buffer.writerIndex();
}
複製程式碼

該類中包裝了一個緩衝區物件和startIndex,startIndex是記錄開始寫入的索引。

2.writtenBytes

public int writtenBytes() {
    return buffer.writerIndex() - startIndex;
}
複製程式碼

該方法是返回寫入了多少資料。

該類裡面還有write方法,都是呼叫了buffer.writeBytes。

(十一)ChannelBufferInputStream

該類繼承了InputStream

1.屬性和建構函式

/**
 * 緩衝區
 */
private final ChannelBuffer buffer;
/**
 * 記錄開始讀資料的索引
 */
private final int startIndex;
/**
 * 結束讀資料的索引
 */
private final int endIndex;

public ChannelBufferInputStream(ChannelBuffer buffer) {
    this(buffer,buffer.readableBytes());
}

public ChannelBufferInputStream(ChannelBuffer buffer,int length) {
    if (buffer == null) {
        throw new NullPointerException("buffer");
    }
    if (length < 0) {
        throw new IllegalArgumentException("length: " + length);
    }
    if (length > buffer.readableBytes()) {
        throw new IndexOutOfBoundsException();
    }

    this.buffer = buffer;
    // 記錄開始讀資料的索引
    startIndex = buffer.readerIndex();
    // 設定結束讀資料的索引
    endIndex = startIndex + length;
    // 標記讀索引
    buffer.markReaderIndex();
}
複製程式碼

該類裡麵包裝了讀開始索引和結束索引,並且在構造方法中初始化這些屬性。

2.readBytes

public int readBytes() {
    return buffer.readerIndex() - startIndex;
}
複製程式碼

該方法是返回讀了多少資料。

3.available

@Override
public int available() throws IOException {
    return endIndex - buffer.readerIndex();
}
複製程式碼

該方法是返回還剩多少資料沒讀

4.read

@Override
public int read() throws IOException {
    if (!buffer.readable()) {
        return -1;
    }
    return buffer.readByte() & 0xff;
}

@Override
public int read(byte[] b,int off,int len) throws IOException {
    // 判斷是否還有資料可讀
    int available = available();
    if (available == 0) {
        return -1;
    }

    // 獲得需要讀取的資料長度
    len = Math.min(available,len);
    buffer.readBytes(b,off,len);
    return len;
}

複製程式碼

該方法是讀資料,返回讀了資料長度。

5.skip

@Override
public long skip(long n) throws IOException {
    if (n > Integer.MAX_VALUE) {
        return skipBytes(Integer.MAX_VALUE);
    } else {
        return skipBytes((int) n);
    }
}

private int skipBytes(int n) throws IOException {
    int nBytes = Math.min(available(),n);
    // 跳過一些資料
    buffer.skipBytes(nBytes);
    return nBytes;
}

複製程式碼

該方法是跳過n長度來讀資料。

後記

該部分相關的原始碼解析地址:github.com/CrazyHZM/in…

該文章講解了Buffer的相關實現邏輯,我很多方法都沒有貼出原始碼,因為很多都是基於Java NIO的ByteBuffer都設計實現,並且要注意AbstractChannelBuffer的三個子類,也就是生成緩衝區的三種形式,還有就是要注意兩個建立緩衝區例項的工廠。下一篇我會講解telnet部分。如果我在哪一部分寫的不夠到位或者寫錯了,歡迎給我提意見。