摘要:當(dāng)你從讀取時(shí),它的將會(huì)被遞增已經(jīng)被讀取的字節(jié)數(shù)。達(dá)到和位于同一位置,表示我們到達(dá)可以讀取的數(shù)據(jù)的末尾。該應(yīng)用程序可以選擇為多個(gè)消息重用相同的消息主體。
ByteBuffer
當(dāng)我們進(jìn)行數(shù)據(jù)傳輸?shù)臅r(shí)候,往往需要使用到緩沖區(qū),常用的緩沖區(qū)就是JDK NIO類庫提供的java.nio.Buffer。
實(shí)際上,7種基礎(chǔ)類型(Boolean除外)都有自己的緩沖區(qū)實(shí)現(xiàn),對(duì)于NIO編程而言,我們主要使用的是ByteBuffer。從功能角度而言,ByteBuffer完全可以滿足NIO編程的需要,但是由于NIO編程的復(fù)雜性,ByteBuffer也有其局限性,它的主要缺點(diǎn)如下。
(1)ByteBuffer長度固定,一旦分配完成,它的容量不能動(dòng)態(tài)擴(kuò)展和收縮,當(dāng)需要編碼的POJO對(duì)象大于ByteBuffer的容量時(shí),會(huì)發(fā)生索引越界異常;
(2)ByteBuffer只有一個(gè)標(biāo)識(shí)位置的指針position,讀寫的時(shí)候需要手工調(diào)用flip()和rewind()等,使用者必須小心謹(jǐn)慎地處理這些API,否則很容易導(dǎo)致程序處理失??;
(3)ByteBuffer的API功能有限,一些高級(jí)和實(shí)用的特性它不支持,需要使用者自己編程實(shí)現(xiàn)。
ByteBuf為了彌補(bǔ)這些不足,Netty提供了自己的ByteBuffer實(shí)現(xiàn)——ByteBuf。
網(wǎng)絡(luò)數(shù)據(jù)的基本單位總是字節(jié)。Java NIO 提供了ByteBuffer 作為它的字節(jié)容器,但是這個(gè)類使用起來過于復(fù)雜,而且也有些繁瑣。
Netty 的ByteBuffer 替代品是ByteBuf,一個(gè)強(qiáng)大的實(shí)現(xiàn),既解決了JDK API 的局限性,又為網(wǎng)絡(luò)應(yīng)用程序的開發(fā)者提供了更好的API。在本章中我們將會(huì)說明和JDK 的ByteBuffer 相比,ByteBuf 的卓越功能性和靈活性。這
也將有助于更好地理解Netty 數(shù)據(jù)處理的一般方式。
Netty 的數(shù)據(jù)處理API 通過兩個(gè)組件暴露——abstract class ByteBuf 和interface ByteBufHolder。
下面是一些ByteBuf API 的優(yōu)點(diǎn):
它可以被用戶自定義的緩沖區(qū)類型擴(kuò)展;
通過內(nèi)置的復(fù)合緩沖區(qū)類型實(shí)現(xiàn)了透明的零拷貝;
容量可以按需增長(類似于JDK 的StringBuilder);
在讀和寫這兩種模式之間切換不需要調(diào)用ByteBuffer 的flip()方法;
讀和寫使用了不同的索引;
支持方法的鏈?zhǔn)秸{(diào)用;
支持引用計(jì)數(shù);
支持池化。
其他類可用于管理ByteBuf 實(shí)例的分配,以及執(zhí)行各種針對(duì)于數(shù)據(jù)容器本身和它所持有的數(shù)據(jù)的操作。我們將在仔細(xì)研究ByteBuf 和ByteBufHolder 時(shí)探討這些特性。
ByteBuf動(dòng)態(tài)擴(kuò)容通常情況下,當(dāng)我們對(duì)ByteBuffer進(jìn)行put操作的時(shí)候,如果緩沖區(qū)剩余可寫空間不夠,就會(huì)發(fā)生BufferOverflowException異常。為了避免發(fā)生這個(gè)問題,通常在進(jìn)行put操作的時(shí)候會(huì)對(duì)剩余可用空間進(jìn)行校驗(yàn),如果剩余空間不足,需要重新創(chuàng)建一個(gè)新的ByteBuffer,并將之前的ByteBuffer復(fù)制到新創(chuàng)建的ByteBuffer中,最后釋放老的ByteBuffer,代碼示例如下。
public ByteBuffer put(ByteBuffer src) { if (src instanceof HeapByteBuffer) { if (src == this) throw new IllegalArgumentException(); HeapByteBuffer sb = (HeapByteBuffer)src; int n = sb.remaining(); if (n > remaining()) throw new BufferOverflowException(); System.arraycopy(sb.hb, sb.ix(sb.position()), hb, ix(position()), n); sb.position(sb.position() + n); position(position() + n); } else if (src.isDirect()) { int n = src.remaining(); if (n > remaining()) throw new BufferOverflowException(); src.get(hb, ix(position()), n); position(position() + n); } else { super.put(src); } return this; }ByteBuf的兩種索引
因?yàn)樗械木W(wǎng)絡(luò)通信都涉及字節(jié)序列的移動(dòng),所以高效易用的數(shù)據(jù)結(jié)構(gòu)明顯是必不可少的。Netty 的ByteBuf 實(shí)現(xiàn)滿足并超越了這些需求。讓我們首先來看看它是如何通過使用不同的索引來簡化對(duì)它所包含的數(shù)據(jù)的訪問的吧。
ByteBuf 維護(hù)了兩個(gè)不同的索引:一個(gè)用于讀取,一個(gè)用于寫入。當(dāng)你從ByteBuf 讀取時(shí),它的readerIndex 將會(huì)被遞增已經(jīng)被讀取的字節(jié)數(shù)。同樣地,當(dāng)你寫入ByteBuf 時(shí),它的writerIndex 也會(huì)被遞增。圖5-1 展示了一個(gè)空ByteBuf 的布局結(jié)構(gòu)和狀態(tài)(一個(gè)讀索引和寫索引都設(shè)置為0 的16 字節(jié)ByteBuf)。
可以看到,正常情況下,一個(gè)ByteBuf被兩個(gè)索引分成三部分。
readerIndex 達(dá)到和writerIndex 位于同一位置,表示我們到達(dá)"可以讀取的"數(shù)據(jù)的末尾。就如同試圖讀取超出數(shù)組末尾的數(shù)據(jù)一樣,試圖讀取超出該點(diǎn)的數(shù)據(jù)將會(huì)觸發(fā)一個(gè)IndexOutOfBoundsException。
名稱以read 或者write 開頭的ByteBuf 方法,將會(huì)推進(jìn)其對(duì)應(yīng)的索引,而名稱以set 或者get 開頭的操作則不會(huì)。后面的這些方法將在作為一個(gè)參數(shù)傳入的一個(gè)相對(duì)索引上執(zhí)行操作。
ByteBuf的三種緩存區(qū)類型和ByteBuffer 一樣,ByteBuf也是一個(gè)緩存區(qū)類,它有三種緩存區(qū)類型:
堆緩存最常用的ByteBuf 模式是將數(shù)據(jù)存儲(chǔ)在JVM 的堆空間中,可以被jvm自動(dòng)回收。這種模式被稱為支撐數(shù)組(backing array),它能在沒有使用池化的情況下提供快速的分配和釋放。這種方式,如代碼清單5-1 所示,非常適合于有遺留的數(shù)據(jù)需要處理的情況。
ByteBuf heapBuf = ...; //檢查ByteBuf 是否有一個(gè)支撐數(shù)組 if (heapBuf.hasArray()) { //如果有,則獲取對(duì)該數(shù)組的引用 byte[] array = heapBuf.array(); //計(jì)算第一個(gè)字節(jié)的偏移量。 int offset = heapBuf.arrayOffset() + heapBuf.readerIndex(); //獲得可讀字節(jié)數(shù) int length = heapBuf.readableBytes(); //使用數(shù)組、偏移量和長度作為參數(shù)調(diào)用你的方法 handleArray(array, offset, length); }
當(dāng)hasArray()方法返回false 時(shí),嘗試訪問支撐數(shù)組將觸發(fā)一個(gè)UnsupportedOperationException。這個(gè)模式類似于JDK 的ByteBuffer 的用法。
堆緩存區(qū)的缺點(diǎn)在于如果進(jìn)行Socket的I/O讀寫,需要額外進(jìn)行一次內(nèi)存復(fù)制,將堆內(nèi)存對(duì)應(yīng)的緩沖區(qū)復(fù)制到內(nèi)核的channel中,性能會(huì)有一定的下降。
直接緩存區(qū) 直接緩存區(qū)和非直接緩存區(qū)的區(qū)別我們先來了解一下什么是直接緩存區(qū):
我們知道java的ByteBuffer類型就有直接和非直接緩存區(qū)這兩種類型。
非直接緩沖區(qū):通過 ByteBuffer的allocate() 方法分配緩沖區(qū),將緩沖區(qū)建立在 JVM 的內(nèi)存中。
直接緩沖區(qū):通過 ByteBuffer的allocateDirect() 方法分配直接緩沖區(qū),將緩沖區(qū)建立在物理內(nèi)存中,不再對(duì)其進(jìn)行復(fù)制,可以提高效率。雖然直接緩沖區(qū)使JVM可以進(jìn)行高效的I/o操作,但它使用的內(nèi)存是操作系統(tǒng)分配的,繞過了JVM堆棧,建立和銷毀比堆棧上的緩沖區(qū)要更大的開銷。
他們的區(qū)別如下:
字節(jié)緩沖區(qū)要么是直接的,要么是非直接的。如果為直接字節(jié)緩沖區(qū),則 Java 虛擬機(jī)會(huì)盡最大努力直接在此緩沖區(qū)上執(zhí)行本機(jī) I/O 操作。也就是說,在每次調(diào)用基礎(chǔ)操作系統(tǒng)的一個(gè)本機(jī) I/O 操作之前(或之后)
直接緩沖區(qū)的內(nèi)容可以駐留在常規(guī)的垃圾回收堆之外,因此,它們對(duì)應(yīng)用程序的內(nèi)存需求量造成的影響可能并不明顯。所以,建議將直接緩沖區(qū)主要分配給那些易受基礎(chǔ)系統(tǒng)的本機(jī) I/O 操作影響的大型、持久的緩沖區(qū)。一般情況下,最好僅在直接緩沖區(qū)能在程序性能方面帶來明顯好處時(shí)分配它們。
ByteBuf 直接緩存區(qū)的使用我們直接看代碼:
ByteBuf directBuf = ...; //檢查ByteBuf 是否由數(shù)組支撐。如果不是,則這是一個(gè)直接緩沖區(qū) if (!directBuf.hasArray()) { //獲取可讀字節(jié)數(shù) int length = directBuf.readableBytes(); //分配一個(gè)新的數(shù)組來保存具有該長度的字節(jié)數(shù)據(jù) byte[] array = new byte[length]; //將字節(jié)復(fù)制到該數(shù)組 directBuf.getBytes(directBuf.readerIndex(), array); //使用數(shù)組、偏移量和長度作為參數(shù)調(diào)用你的方法 handleArray(array, 0, length); }復(fù)合緩沖區(qū)
第三種也是最后一種模式使用的是復(fù)合緩沖區(qū),它為多個(gè)ByteBuf 提供一個(gè)聚合視圖。在這里你可以根據(jù)需要添加或者刪除ByteBuf 實(shí)例,這是一個(gè)JDK 的ByteBuffer 沒有的特性。
Netty 通過一個(gè)ByteBuf 子類——CompositeByteBuf——實(shí)現(xiàn)了這個(gè)模式,它提供了一個(gè)將多個(gè)緩沖區(qū)表示為單個(gè)合并緩沖區(qū)的虛擬表示。
為了舉例說明,讓我們考慮一下一個(gè)由兩部分——頭部和主體——組成的將通過HTTP 協(xié)議傳輸?shù)南?。這兩部分由應(yīng)用程序的不同模塊產(chǎn)生,將會(huì)在消息被發(fā)送的時(shí)候組裝。該應(yīng)用程序可以選擇為多個(gè)消息重用相同的消息主體。當(dāng)這種情況發(fā)生時(shí),對(duì)于每個(gè)消息都將會(huì)創(chuàng)建一個(gè)新的頭部。
因?yàn)槲覀儾幌霝槊總€(gè)消息都重新分配這兩個(gè)緩沖區(qū),所以使用CompositeByteBuf 是一個(gè)完美的選擇。它在消除了沒必要的復(fù)制的同時(shí),暴露了通用的ByteBuf API。圖5-2 展示了生成的消息布局。
代碼清單5-3 展示了如何通過使用JDK 的ByteBuffer 來實(shí)現(xiàn)這一需求。創(chuàng)建了一個(gè)包含兩個(gè)ByteBuffer 的數(shù)組用來保存這些消息組件,同時(shí)創(chuàng)建了第三個(gè)ByteBuffer 用來保存所有這些數(shù)據(jù)的副本。
// Use an array to hold the message parts ByteBuffer[] message = new ByteBuffer[] { header, body }; // Create a new ByteBuffer and use copy to merge the header and body ByteBuffer message2 = ByteBuffer.allocate(header.remaining() + body.remaining()); message2.put(header); message2.put(body); message 2.flip();
分配和復(fù)制操作,以及伴隨著對(duì)數(shù)組管理的需要,使得這個(gè)版本的實(shí)現(xiàn)效率低下而且笨拙。
代碼清單5-4 展示了一個(gè)使用了CompositeByteBuf 的版本。
CompositeByteBuf messageBuf = Unpooled.compositeBuffer(); //將ByteBuf 實(shí)例追加到CompositeByteBuf ByteBuf headerBuf = ...; // can be backing or direct ByteBuf bodyBuf = ...; // can be backing or direct messageBuf.addComponents(headerBuf, bodyBuf); ..... //刪除位于索引位置為 0(第一個(gè)組件)的ByteBuf messageBuf.removeComponent(0); // remove the header //循環(huán)遍歷所有的ByteBuf 實(shí)例 for (ByteBuf buf : messageBuf) { System.out.println(buf.toString()); }復(fù)合緩存區(qū)的使用
CompositeByteBuf 可能不支持訪問其支撐數(shù)組,因此訪問CompositeByteBuf 中的數(shù)據(jù)類似于(訪問)直接緩沖區(qū)的模式,如代碼清單5-5 所示。
CompositeByteBuf compBuf = Unpooled.compositeBuffer(); //獲得可讀字節(jié)數(shù) int length = compBuf.readableBytes(); //分配一個(gè)具有可讀字節(jié)數(shù)長度的新數(shù)組 byte[] array = new byte[length]; //將字節(jié)讀到該數(shù)組中 compBuf.getBytes(compBuf.readerIndex(), array); //使用偏移量和長度作為參數(shù)使用該數(shù)組 handleArray(array, 0, array.length);總結(jié)
經(jīng)驗(yàn)表明:ByteBuf的最佳實(shí)踐是在I/O通信線程的讀寫緩沖區(qū)使用DirectByteBuf,后端業(yè)務(wù)消息的編解碼模塊使用HeapByteBuf
字節(jié)級(jí)操作ByteBuf 提供了許多超出基本讀、寫操作的方法用于修改它的數(shù)據(jù)。在接下來的章節(jié)中,我們將會(huì)討論這些中最重要的部分。
通過索引訪問數(shù)據(jù)如同在普通的Java 字節(jié)數(shù)組中一樣,ByteBuf 的索引是從零開始的:第一個(gè)字節(jié)的索引是0,最后一個(gè)字節(jié)的索引總是capacity() - 1。代碼清單5-6 表明,對(duì)存儲(chǔ)機(jī)制的封裝使得遍歷ByteBuf 的內(nèi)容非常簡單。
ByteBuf buffer = ...; for (int i = 0; i < buffer.capacity(); i++) { byte b = buffer.getByte(i); System.out.println((char)b); }
需要注意的是,使用那些需要一個(gè)索引值參數(shù)的方法來訪問數(shù)據(jù)既不會(huì)改變r(jià)eaderIndex 也不會(huì)改變writerIndex。如果有需要,也可以通過調(diào)用readerIndex(index)或者writerIndex(index)來手動(dòng)移動(dòng)這兩者。
通過數(shù)據(jù)反查索引在ByteBuf中有多種可以用來確定指定值的索引的方法。最簡單的是使用indexOf()方法。較復(fù)雜的查找可以通過那些需要一個(gè)ByteBufProcessor作為參數(shù)的方法達(dá)成。這個(gè)接口只定義了一個(gè)方法:
boolean process(byte value)
它將檢查輸入值是否是正在查找的值。
ByteBufProcessor針對(duì)一些常見的值定義了許多便利的枚舉。假設(shè)你的應(yīng)用程序需要和所謂的包含有以NULL結(jié)尾的內(nèi)容的Flash套接字,可以調(diào)用:
forEach Byte(ByteBufProcessor.FIND_NUL)
如代碼清單展示了一個(gè)查找回車符(r)的索引的例子。:
ByteBuf buffer = ...; int index = buffer.forEachByte(ByteBufProcessor.FIND_CR);常規(guī)讀/寫操作
正如我們所提到過的,有兩種類別的讀/寫操作:
get()和set()操作,從給定的索引開始,并且保持索引不變;
read()和write()操作,從給定的索引開始,并且會(huì)根據(jù)已經(jīng)訪問過的字節(jié)數(shù)對(duì)索引進(jìn)行調(diào)整。
get()和set()操作表5-1 列舉了最常用的get()方法。完整列表請(qǐng)參考對(duì)應(yīng)的API 文檔。
這里面getBytes方法我們需要強(qiáng)調(diào)一下,比如buf.getBytes(buf.readerIndex(), array);表示將從buf實(shí)例的readerIndex為起點(diǎn)的數(shù)據(jù)傳入指定的目的地(一個(gè)數(shù)組中)。
read()和write()操作現(xiàn)在,讓我們研究一下read()操作,其作用于當(dāng)前的readerIndex 或writerIndex。這些方法將用于從ByteBuf 中讀取數(shù)據(jù),如同它是一個(gè)流。表5-3 展示了最常用的方法。
幾乎每個(gè)read()方法都有對(duì)應(yīng)的write()方法,用于將數(shù)據(jù)追加到ByteBuf 中。注意,表5-4 中所列出的這些方法的參數(shù)是需要寫入的值,而不是索引值
刪除已讀字節(jié)正如我們之前看過的這張圖:
在上圖中標(biāo)記為可丟棄字節(jié)的分段包含了已經(jīng)被讀過的字節(jié)。通過調(diào)用discardReadBytes()方法,可以丟棄它們并回收空間。這個(gè)分段的初始大小為0,存儲(chǔ)在readerIndex 中,會(huì)隨著read 操作的執(zhí)行而增加(get*操作不會(huì)移動(dòng)readerIndex)。
上圖展示了下圖中所展示的緩沖區(qū)上調(diào)用discardReadBytes()方法后的結(jié)果??梢钥吹?,可丟棄字節(jié)分段中的空間已經(jīng)變?yōu)榭蓪懙牧恕W⒁?,在調(diào)用discardReadBytes()之后,對(duì)可寫分段的內(nèi)容并沒有任何的保證。
雖然你可能會(huì)傾向于頻繁地調(diào)用discardReadBytes()方法以確保可寫分段的最大化,但是請(qǐng)注意,這將極有可能會(huì)導(dǎo)致內(nèi)存復(fù)制,因?yàn)榭勺x字節(jié)(圖中標(biāo)記為CONTENT 的部分)必須被移動(dòng)到緩沖區(qū)的開始位置。我們建議只在有真正需要的時(shí)候才這樣做,例如,當(dāng)內(nèi)存非常寶貴的時(shí)候。
ByteBuf 的可讀字節(jié)分段存儲(chǔ)了實(shí)際數(shù)據(jù)。新分配的、包裝的或者復(fù)制的緩沖區(qū)的默認(rèn)的readerIndex 值為0。任何名稱以read 或者skip 開頭的操作都將檢索或者跳過位于當(dāng)前readerIndex 的數(shù)據(jù),并且將它增加已讀字節(jié)數(shù)。
以下代碼清單展示了如何讀取所有可以讀的字節(jié)。
ByteBuf buffer = ...; while (buffer.isReadable()) { System.out.println(buffer.readByte()); }寫數(shù)據(jù)
可寫字節(jié)分段是指一個(gè)擁有未定義內(nèi)容的、寫入就緒的內(nèi)存區(qū)域。新分配的緩沖區(qū)的writerIndex 的默認(rèn)值為0。任何名稱以write 開頭的操作都將從當(dāng)前的writerIndex 處開始寫數(shù)據(jù),并將它增加已經(jīng)寫入的字節(jié)數(shù)。如果嘗試往目標(biāo)寫入超過目標(biāo)容量的數(shù)據(jù),將會(huì)引發(fā)一個(gè)IndexOutOfBoundException。
以下代碼清單是一個(gè)用隨機(jī)整數(shù)值填充緩沖區(qū),直到它空間不足為止的例子。writeableBytes()方法在這里被用來確定該緩沖區(qū)中是否還有足夠的空間。
// Fills the writable bytes of a buffer with random integers. ByteBuf buffer = ...; //因?yàn)橐粋€(gè)int為四個(gè)字節(jié) while (buffer.writableBytes() >= 4) { buffer.writeInt(random.nextInt()); }手動(dòng)設(shè)置索引
JDK 的InputStream 定義了mark(int readlimit)和reset()方法,這些方法分別被用來將流中的當(dāng)前位置標(biāo)記為指定的值,以及將流重置到該位置。
同樣,可以通過調(diào)用markReaderIndex()、markWriterIndex()、resetWriterIndex()和resetReaderIndex()來標(biāo)記和重置ByteBuf 的readerIndex 和writerIndex。這些和InputStream 上的調(diào)用類似,只是沒有readlimit 參數(shù)來指定標(biāo)記什么時(shí)候失效。
也可以通過調(diào)用readerIndex(int)或者writerIndex(int)來將索引移動(dòng)到指定位置。試圖將任何一個(gè)索引設(shè)置到一個(gè)無效的位置都將導(dǎo)致一個(gè)IndexOutOfBoundsException??梢酝ㄟ^調(diào)用clear()方法來將readerIndex 和writerIndex 都設(shè)置為0。注意,這并不會(huì)清除內(nèi)存中的內(nèi)容。
調(diào)用clear()比調(diào)用discardReadBytes()輕量得多,因?yàn)樗鼘⒅皇侵刂盟饕粫?huì)復(fù)制任何的內(nèi)存。
復(fù)制指向緩存區(qū)的指針派生緩沖區(qū)為ByteBuf 提供了以專門的方式來呈現(xiàn)該ByteBuf內(nèi)容的視圖。這類視圖可以通過以下方法被創(chuàng)建的:
duplicate();
slice();獲取調(diào)用者的子緩沖區(qū),且與原緩沖區(qū)共享緩沖區(qū)
slice(int, int);獲取調(diào)用者的子緩沖區(qū),且與原緩沖區(qū)共享緩沖區(qū)
Unpooled.unmodifiableBuffer(…);
order(ByteOrder);
readSlice(int)。
每個(gè)這些方法都將返回一個(gè)新的ByteBuf 實(shí)例,它具有自己的讀索引、寫索引和標(biāo)記索引。其內(nèi)部存儲(chǔ)和JDK 的ByteBuffer一樣也是共享的。這使得派生緩沖區(qū)的創(chuàng)建成本是很低廉的,但是這也意味著,如果你修改了它的內(nèi)容,也同時(shí)修改了其對(duì)應(yīng)的源實(shí)例,所以要小心。
Charset utf8 = Charset.forName("UTF-8"); //創(chuàng)建一個(gè)ByteBuf "Netty in Action" ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8); //創(chuàng)建該ByteBuf 從索引0 開始到索引15結(jié)束的一個(gè)新切片 ByteBuf sliced = buf.slice(0, 15); System.out.println(sliced.toString(utf8)); //更新索引0 處的字節(jié) buf.setByte(0, (byte)"J"); //將會(huì)成功,因?yàn)閿?shù)據(jù)是共享的,對(duì)其中一個(gè)所做的更改對(duì)另外一個(gè)也是可見的 assert buf.getByte(0) == sliced.getByte(0);復(fù)制緩存區(qū)的內(nèi)容
如果需要一個(gè)現(xiàn)有緩沖區(qū)的真實(shí)副本,請(qǐng)使用copy()或者copy(int, int)方法。不同于派生緩沖區(qū),由這個(gè)調(diào)用所返回的ByteBuf 擁有獨(dú)立的數(shù)據(jù)副本。
Charset utf8 = Charset.forName("UTF-8"); ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8); ByteBuf copy = buf.copy(0, 15); System.out.println(copy.toString(utf8)); buf.setByte(0, (byte) "J"); //將會(huì)成功,因?yàn)閿?shù)據(jù)不是共享的 assert buf.getByte(0) != copy.getByte(0);
如果我們不修改原始ByteBuf 的切片或者副本,這兩種場(chǎng)景是相同的。只要有可能,我們盡量使用slice()方法來避免復(fù)制內(nèi)存的開銷。
其他api ByteBufHolder 接口我們經(jīng)常發(fā)現(xiàn),除了實(shí)際的數(shù)據(jù)負(fù)載之外,我們還需要存儲(chǔ)各種屬性值。HTTP 響應(yīng)便是一個(gè)很好的例子,除了表示為字節(jié)的內(nèi)容,還包括狀態(tài)碼、cookie 等。
為了處理這種常見的用例,Netty 提供了ByteBufHolder,我們可以看看他的默認(rèn)實(shí)現(xiàn):
可以看出,它主要就是封裝了一個(gè)ByteBuf對(duì)象,以及對(duì)這個(gè)對(duì)象的一些操作api?,F(xiàn)在假如我們要構(gòu)造一個(gè)HTTP響應(yīng)的對(duì)象,那么就可以在繼承ByteBufHolder的基礎(chǔ)上在拓展其他的比如狀態(tài)碼、cookie等字段,達(dá)到自己的目的。
它常用的api如下:
在這一節(jié)中,我們將描述管理ByteBuf 實(shí)例的不同方式。
按需分配:ByteBufAllocator 接口為了降低分配和釋放內(nèi)存的開銷,Netty 通過interface ByteBufAllocator 實(shí)現(xiàn)了(ByteBuf 的)池化,它可以用來分配我們所描述過的任意類型的ByteBuf 實(shí)例。
關(guān)于ioBuffer,默認(rèn)地,當(dāng)所運(yùn)行的環(huán)境具有sun.misc.Unsafe 支持時(shí),返回基于直接內(nèi)存存儲(chǔ)的ByteBuf,否則返回基于堆內(nèi)存存儲(chǔ)的ByteBuf;當(dāng)指定使用PreferHeapByteBufAllocator 時(shí),則只會(huì)返回基于堆內(nèi)存存儲(chǔ)的ByteBuf。
我們可以通過Channel(每個(gè)都可以有一個(gè)不同的ByteBufAllocator 實(shí)例)或者綁定到ChannelHandler 的ChannelHandlerContext 獲取一個(gè)到ByteBufAllocator 的引用。代碼清單5-14 說明了這兩種方法。
獲取一個(gè)到ByteBufAllocator 的引用
//從Channel 獲取一個(gè)到ByteBufAllocator 的引用 Channel channel = ...; ByteBufAllocator allocator = channel.alloc(); .... //從ChannelHandlerContext 獲取一個(gè)到ByteBufAllocator 的引用 ChannelHandlerContext ctx = ...; ByteBufAllocator allocator2 = ctx.alloc(); ...
Netty提供了兩種ByteBufAllocator的實(shí)現(xiàn):PooledByteBufAllocator和UnpooledByteBufAllocator。前者池化了ByteBuf的實(shí)例以提高性能并最大限度地減少內(nèi)存碎片。此實(shí)現(xiàn)使用了一種稱為jemalloc的已被大量現(xiàn)代操作系統(tǒng)所采用的高效方法來分配內(nèi)存。后者的實(shí)現(xiàn)不池化ByteBuf實(shí)例,并且在每次它被調(diào)用時(shí)都會(huì)返回一個(gè)新的實(shí)例。
Netty默認(rèn)使用了PooledByteBufAllocator
Unpooled 緩沖區(qū)可能某些情況下,你未能獲取一個(gè)到ByteBufAllocator 的引用。對(duì)于這種情況,Netty 提供了一個(gè)簡單的稱為Unpooled 的工具類,它提供了靜態(tài)的輔助方法來創(chuàng)建未池化的ByteBuf實(shí)例。表5-8 列舉了這些中最重要的方法。
Unpooled 類還使得ByteBuf 同樣可用于那些并不需要Netty 的其他組件的非網(wǎng)絡(luò)項(xiàng)目,使得其能得益于高性能的可擴(kuò)展的緩沖區(qū)API。
ByteBufUtil 類ByteBufUtil 提供了用于操作ByteBuf 的靜態(tài)的輔助方法。因?yàn)檫@個(gè)API 是通用的,并且和池化無關(guān),所以這些方法已然在分配類的外部實(shí)現(xiàn)。
這些靜態(tài)方法中最有價(jià)值的可能就是hexdump()方法,它以十六進(jìn)制的表示形式打印ByteBuf 的內(nèi)容。這在各種情況下都很有用,例如,出于調(diào)試的目的記錄ByteBuf 的內(nèi)容。十六進(jìn)制的表示通常會(huì)提供一個(gè)比字節(jié)值的直接表示形式更加有用的日志條目,此外,十六進(jìn)制的版本還可以很容易地轉(zhuǎn)換回實(shí)際的字節(jié)表示。
另一個(gè)有用的方法是boolean equals(ByteBuf, ByteBuf),它被用來判斷兩個(gè)ByteBuf實(shí)例的相等性。如果你實(shí)現(xiàn)自己的ByteBuf 子類,你可能會(huì)發(fā)現(xiàn)ByteBufUtil 的其他有用方法。
引用計(jì)數(shù)引用計(jì)數(shù)是一種通過在某個(gè)對(duì)象所持有的資源不再被其他對(duì)象引用時(shí)釋放該對(duì)象所持有的資源來優(yōu)化內(nèi)存使用和性能的技術(shù)。Netty 在第4 版中為ByteBuf 和ByteBufHolder 引入了引用計(jì)數(shù)技術(shù),它們都實(shí)現(xiàn)了interface ReferenceCounted。
引用計(jì)數(shù)背后的想法并不是特別的復(fù)雜;它主要涉及跟蹤到某個(gè)特定對(duì)象的活動(dòng)引用的數(shù)量。一個(gè)ReferenceCounted 實(shí)現(xiàn)的實(shí)例將通常以活動(dòng)的引用計(jì)數(shù)為1 作為開始。只要引用計(jì)數(shù)大于0,就能保證對(duì)象不會(huì)被釋放。當(dāng)活動(dòng)引用的數(shù)量減少到0 時(shí),該實(shí)例就會(huì)被釋放。注意,雖然釋放的確切語義可能是特定于實(shí)現(xiàn)的,但是至少已經(jīng)釋放的對(duì)象應(yīng)該不可再用了。
引用計(jì)數(shù)對(duì)于池化實(shí)現(xiàn)(如PooledByteBufAllocator)來說是至關(guān)重要的,它降低了內(nèi)存分配的開銷。代碼清單5-15 展示了相關(guān)的示例。
//從Channel 獲取ByteBufAllocator Channel channel = ...; ByteBufAllocator allocator = channel.alloc(); .... //從ByteBufAllocator分配一個(gè)ByteBuf ByteBuf buffer = allocator.directBuffer(); //檢查引用計(jì)數(shù)是否為預(yù)期的1 assert buffer.refCnt() == 1; ... //減少到該對(duì)象的活動(dòng)引用。當(dāng)減少到0 時(shí),該對(duì)象被釋放,并且該方法返回true ByteBuf buffer = ...; boolean released = buffer.release();
試圖訪問一個(gè)已經(jīng)被釋放的引用計(jì)數(shù)的對(duì)象,將會(huì)導(dǎo)致一個(gè)IllegalReferenceCountException。
注意,一個(gè)特定的(ReferenceCounted 的實(shí)現(xiàn))類,可以用它自己的獨(dú)特方式來定義它的引用計(jì)數(shù)規(guī)則。例如,我們可以設(shè)想一個(gè)類,其release()方法的實(shí)現(xiàn)總是將引用計(jì)數(shù)設(shè)為零,而不用關(guān)心它的當(dāng)前值,從而一次性地使所有的活動(dòng)引用都失效。
誰負(fù)責(zé)釋放release呢 一般來說,是由最后訪問(引用計(jì)數(shù))對(duì)象的那一方來負(fù)責(zé)將它釋放。在第6 章中,
我們將會(huì)解釋這個(gè)概念和ChannelHandler 以及ChannelPipeline 的相關(guān)性。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/69577.html
摘要:它使用了事件通知以確定在一組非阻塞套接字中有哪些已經(jīng)就緒能夠進(jìn)行相關(guān)的操作。目前,可以把看作是傳入入站或者傳出出站數(shù)據(jù)的載體。出站事件是未來將會(huì)觸發(fā)的某個(gè)動(dòng)作的操作結(jié)果,這些動(dòng)作包括打開或者關(guān)閉到遠(yuǎn)程節(jié)點(diǎn)的連接將數(shù)據(jù)寫到或者沖刷到套接字。 netty的概念 定義 Netty 是一款異步的事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用程序框架,支持快速地開發(fā)可維護(hù)的高性能的面向協(xié)議的服務(wù)器和客戶端。我們可以很簡單的...
摘要:是一個(gè)分布式服務(wù)框架,以及治理方案。手寫注意要點(diǎn)手寫注意要點(diǎn)基于上文中對(duì)于協(xié)議的理解,如果我們自己去實(shí)現(xiàn),需要考慮哪些技術(shù)呢其實(shí)基于圖的整個(gè)流程應(yīng)該有一個(gè)大概的理解?;谑謱憣?shí)現(xiàn)基于手寫實(shí)現(xiàn)理解了協(xié)議后,我們基于來實(shí)現(xiàn)一個(gè)通信框架。閱讀這篇文章之前,建議先閱讀和這篇文章關(guān)聯(lián)的內(nèi)容。[1]詳細(xì)剖析分布式微服務(wù)架構(gòu)下網(wǎng)絡(luò)通信的底層實(shí)現(xiàn)原理(圖解)[2][年薪60W的技巧]工作了5年,你真的理解N...
摘要:使用來優(yōu)化套接字操作,盡可能消除由的緩沖區(qū)實(shí)現(xiàn)所導(dǎo)致的性能以及內(nèi)存使用率的懲罰,這種優(yōu)化發(fā)生在的核心代碼中,不會(huì)被暴露出來。當(dāng)前將會(huì)被增加所寫入的字節(jié)數(shù)。 ByteBuf是Java NIO ByteBuffer的替代品,是網(wǎng)絡(luò)數(shù)據(jù)基本單位字節(jié)的容器。 ByteBuf的API Netty的數(shù)據(jù)處理API通過兩個(gè)組件暴漏:抽象類ByteBuf和接口ByteBufHolder ByteBuf...
摘要:是一個(gè)高性能事件驅(qū)動(dòng)的異步的非堵塞的框架,用于建立等底層的連接,基于可以建立高性能的服務(wù)器。在中提供了兩套,一套是針對(duì)標(biāo)準(zhǔn)輸入輸出,另一套就是網(wǎng)絡(luò)編程。和是標(biāo)準(zhǔn)中的核心對(duì)象是對(duì)原中流的模擬,任何來源和目的數(shù)據(jù)都必須通過一個(gè)對(duì)象。 Netty是一個(gè)高性能 事件驅(qū)動(dòng)的異步的非堵塞的IO(NIO)框架,用于建立TCP等底層的連接,基于Netty可以建立高性能的Http服務(wù)器。1、首先來復(fù)習(xí)下...
摘要:如果什么事都沒得做,它也不會(huì)死循環(huán),它會(huì)將線程休眠起來,直到下一個(gè)事件來了再繼續(xù)干活,這樣的一個(gè)線程稱之為線程。而請(qǐng)求處理邏輯既可以使用單獨(dú)的線程池進(jìn)行處理,也可以跟放在讀寫線程一塊處理。 Netty到底是什么 從HTTP說起 有了Netty,你可以實(shí)現(xiàn)自己的HTTP服務(wù)器,F(xiàn)TP服務(wù)器,UDP服務(wù)器,RPC服務(wù)器,WebSocket服務(wù)器,Redis的Proxy服務(wù)器,MySQL的P...
閱讀 1391·2021-11-04 16:11
閱讀 3050·2021-10-12 10:11
閱讀 2986·2021-09-29 09:47
閱讀 1622·2021-09-22 15:40
閱讀 1021·2019-08-29 15:43
閱讀 2812·2019-08-29 13:50
閱讀 1588·2019-08-29 13:28
閱讀 2698·2019-08-29 12:54