因為所有的網絡通信最終都是基于底層的字節流傳輸,因此一個高效、方便、易用的數據接口是必要的,而 Netty 的 ByteBuf 滿足這些需求。
ByteBuf 是一個很好的經過優化的數據容器,我們可以將字節數據有效的添加到 ByteBuf 中或從 ByteBuf 中獲取數據。ByteBuf 有2部分:一個用于讀,一個用于寫。我們可以按順序的讀取數據,也可以通過調整讀取數據的索引或者直接將讀取位置索引作為參數傳遞給get方法來重復讀取數據。
### [](https://github.com/waylau/essential-netty-in-action/blob/master/CORE%20FUNCTIONS/ByteBuf%20-%20The%20byte%20data%20container.md#bytebuf-如何在工作)ByteBuf 如何在工作?
寫入數據到 ByteBuf 后,writerIndex(寫入索引)增加。開始讀字節后,readerIndex(讀取索引)增加。你可以讀取字節,直到寫入索引和讀取索引處在相同的位置,ByteBuf 變為不可讀。當訪問數據超過數組的最后位,則會拋出 IndexOutOfBoundsException。
調用 ByteBuf 的 "read" 或 "write" 開頭的任何方法都會提升 相應的索引。另一方面,"set" 、 "get"操作字節將不會移動索引位置;他們只會操作相關的通過參數傳入方法的相對索引。
可以給ByteBuf指定一個最大容量值,這個值限制著ByteBuf的容量。任何嘗試將寫入索引超過這個值的行為都將導致拋出異常。ByteBuf 的默認最大容量限制是 Integer.MAX_VALUE。
ByteBuf 類似于一個字節數組,最大的區別是讀和寫的索引可以用來控制對緩沖區數據的訪問。下圖顯示了一個容量為16的空的 ByteBuf 的布局和狀態,writerIndex 和 readerIndex 都在索引位置 0 :
Figure 5.1 A 16-byte ByteBuf with its indices set to 0
[](https://github.com/waylau/essential-netty-in-action/blob/master/images/Figure%205.1%20A%2016-byte%20ByteBuf%20with%20its%20indices%20set%20to%200.jpg)
### [](https://github.com/waylau/essential-netty-in-action/blob/master/CORE%20FUNCTIONS/ByteBuf%20-%20The%20byte%20data%20container.md#bytebuf-使用模式)ByteBuf 使用模式
#### [](https://github.com/waylau/essential-netty-in-action/blob/master/CORE%20FUNCTIONS/ByteBuf%20-%20The%20byte%20data%20container.md#heap-buffer堆緩沖區)HEAP BUFFER(堆緩沖區)
最常用的模式是 ByteBuf 將數據存儲在 JVM 的堆空間,這是通過將數據存儲在數組的實現。堆緩沖區可以快速分配,當不使用時也可以快速釋放。它還提供了直接訪問數組的方法,通過 ByteBuf.array() 來獲取 byte[]數據。 這種方法,正如清單5.1中所示的那樣,是非常適合用來處理遺留數據的。
Listing 5.1 Backing array
~~~
ByteBuf heapBuf = ...;
if (heapBuf.hasArray()) { //1
byte[] array = heapBuf.array(); //2
int offset = heapBuf.arrayOffset() + heapBuf.readerIndex(); //3
int length = heapBuf.readableBytes();//4
handleArray(array, offset, length); //5
}
~~~
1.檢查 ByteBuf 是否有支持數組。
2.如果有的話,得到引用數組。
3.計算第一字節的偏移量。
4.獲取可讀的字節數。
5.使用數組,偏移量和長度作為調用方法的參數。
注意:
* 訪問非堆緩沖區 ByteBuf 的數組會導致UnsupportedOperationException, 可以使用 ByteBuf.hasArray()來檢查是否支持訪問數組。
* 這個用法與 JDK 的 ByteBuffer 類似
#### [](https://github.com/waylau/essential-netty-in-action/blob/master/CORE%20FUNCTIONS/ByteBuf%20-%20The%20byte%20data%20container.md#direct-buffer直接緩沖區)DIRECT BUFFER(直接緩沖區)
“直接緩沖區”是另一個 ByteBuf 模式。對象的所有內存分配發生在 堆,對不對?好吧,并非總是如此。在 JDK1.4 中被引入 NIO 的ByteBuffer 類允許 JVM 通過本地方法調用分配內存,其目的是
* 通過免去中間交換的內存拷貝, 提升IO處理速度; 直接緩沖區的內容可以駐留在垃圾回收掃描的堆區以外。
* DirectBuffer 在 -XX:MaxDirectMemorySize=xxM大小限制下, 使用 Heap 之外的內存, GC對此”無能為力”,也就意味著規避了在高負載下頻繁的GC過程對應用線程的中斷影響.(詳見[http://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html.](http://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html.))
這就解釋了為什么“直接緩沖區”對于那些通過 socket 實現數據傳輸的應用來說,是一種非常理想的方式。如果你的數據是存放在堆中分配的緩沖區,那么實際上,在通過 socket 發送數據之前,JVM 需要將先數據復制到直接緩沖區。
但是直接緩沖區的缺點是在內存空間的分配和釋放上比堆緩沖區更復雜,另外一個缺點是如果要將數據傳遞給遺留代碼處理,因為數據不是在堆上,你可能不得不作出一個副本,如下:
Listing 5.2 Direct buffer data access
~~~
ByteBuf directBuf = ...
if (!directBuf.hasArray()) { //1
int length = directBuf.readableBytes();//2
byte[] array = new byte[length]; //3
directBuf.getBytes(directBuf.readerIndex(), array); //4
handleArray(array, 0, length); //5
}
~~~
1.檢查 ByteBuf 是不是由數組支持。如果不是,這是一個直接緩沖區。
2.獲取可讀的字節數
3.分配一個新的數組來保存字節
4.字節復制到數組
5.將數組,偏移量和長度作為參數調用某些處理方法
顯然,這比使用數組要多做一些工作。因此,如果你事前就知道容器里的數據將作為一個數組被訪問,你可能更愿意使用堆內存。
#### [](https://github.com/waylau/essential-netty-in-action/blob/master/CORE%20FUNCTIONS/ByteBuf%20-%20The%20byte%20data%20container.md#composite-buffer復合緩沖區)COMPOSITE BUFFER(復合緩沖區)
最后一種模式是復合緩沖區,我們可以創建多個不同的 ByteBuf,然后提供一個這些 ByteBuf 組合的視圖。復合緩沖區就像一個列表,我們可以動態的添加和刪除其中的 ByteBuf,JDK 的 ByteBuffer 沒有這樣的功能。
Netty 提供了 ByteBuf 的子類 CompositeByteBuf 類來處理復合緩沖區,CompositeByteBuf 只是一個視圖。
*警告*
*CompositeByteBuf.hasArray() 總是返回 false,因為它可能既包含堆緩沖區,也包含直接緩沖區*
例如,一條消息由 header 和 body 兩部分組成,將 header 和 body 組裝成一條消息發送出去,可能 body 相同,只是 header 不同,使用CompositeByteBuf 就不用每次都重新分配一個新的緩沖區。下圖顯示CompositeByteBuf 組成 header 和 body:
Figure 5.2 CompositeByteBuf holding a header and body
[](https://github.com/waylau/essential-netty-in-action/blob/master/images/Figure%205.2%20CompositeByteBuf%20holding%20a%20header%20and%20body.jpg)
下面代碼顯示了使用 JDK 的 ByteBuffer 的一個實現。兩個 ByteBuffer 的數組創建保存消息的組件,第三個創建用于保存所有數據的副本。
Listing 5.3 Composite buffer pattern using ByteBuffer
~~~
// 使用數組保存消息的各個部分
ByteBuffer[] message = { header, body };
// 使用副本來合并這兩個部分
ByteBuffer message2 = ByteBuffer.allocate(
header.remaining() + body.remaining());
message2.put(header);
message2.put(body);
message2.flip();
~~~
這種做法顯然是低效的;分配和復制操作不是最優的方法,操縱數組使代碼顯得很笨拙。
下面看使用 CompositeByteBuf 的改進版本
Listing 5.4 Composite buffer pattern using CompositeByteBuf
~~~
CompositeByteBuf messageBuf = ...;
ByteBuf headerBuf = ...; // 可以支持或直接
ByteBuf bodyBuf = ...; // 可以支持或直接
messageBuf.addComponents(headerBuf, bodyBuf);
// ....
messageBuf.removeComponent(0); // 移除頭 //2
for (int i = 0; i < messageBuf.numComponents(); i++) { //3
System.out.println(messageBuf.component(i).toString());
}
~~~
1.追加 ByteBuf 實例的 CompositeByteBuf
2.刪除 索引1的 ByteBuf
3.遍歷所有 ByteBuf 實例。
清單5.4 所示,你可以簡單地把 CompositeByteBuf 當作一個可迭代遍歷的容器。 CompositeByteBuf 不允許訪問其內部可能存在的支持數組,也不允許直接訪問數據,這一點類似于直接緩沖區模式,如圖5.5所示。
Listing 5.5 Access data
~~~
CompositeByteBuf compBuf = ...;
int length = compBuf.readableBytes(); //1
byte[] array = new byte[length]; //2
compBuf.getBytes(compBuf.readerIndex(), array); //3
handleArray(array, 0, length); //4
~~~
1.得到的可讀的字節數。
2.分配一個新的數組,數組長度為可讀字節長度。
3.讀取字節到數組
4.使用數組,把偏移量和長度作為參數
Netty 嘗試使用 CompositeByteBuf 優化 socket I/O 操作,消除 原生 JDK 中可能存在的的性能低和內存消耗問題。雖然這是在Netty 的核心代碼中進行的優化,并且是不對外暴露的,但是作為開發者還是應該意識到其影響。
*CompositeByteBuf API*
*CompositeByteBuf 提供了大量的附加功能超出了它所繼承的 ByteBuf。請參閱的 Netty 的 Javadoc 文檔 API。*
- Introduction
- 開始
- Netty-異步和數據驅動
- Netty 介紹
- 構成部分
- 關于本書
- 第一個 Netty 應用
- 設置開發環境
- Netty 客戶端/服務端 總覽
- 寫一個 echo 服務器
- 寫一個 echo 客戶端
- 編譯和運行 Echo 服務器和客戶端
- 總結
- Netty 總覽
- Netty 快速入門
- Channel, Event 和 I/O
- 什么是 Bootstrapping 為什么要用
- ChannelHandler 和 ChannelPipeline
- 近距離觀察 ChannelHandler
- 總結
- 核心功能
- Transport(傳輸)
- 案例研究:Transport 的遷移
- Transport API
- 包含的 Transport
- Transport 使用情況
- 總結
- Buffer(緩沖)
- Buffer API
- ByteBuf - 字節數據的容器
- 字節級別的操作
- ByteBufHolder
- ByteBuf 分配
- 總結
- ChannelHandler 和 ChannelPipeline
- ChannelHandler 家族
- ChannelPipeline
- ChannelHandlerContext
- 總結
- Codec 框架
- 什么是 Codec
- Decoder(解碼器)
- Encoder(編碼器)
- 抽象 Codec(編解碼器)類
- 總結
- 提供了的 ChannelHandler 和 Codec
- 使用 SSL/TLS 加密 Netty 程序
- 構建 Netty HTTP/HTTPS 應用
- 空閑連接以及超時
- 解碼分隔符和基于長度的協議
- 編寫大型數據
- 序列化數據
- 總結
- Bootstrap 類型
- 引導客戶端和無連接協議
- 引導服務器
- 從 Channel 引導客戶端
- 在一個引導中添加多個 ChannelHandler
- 使用Netty 的 ChannelOption 和屬性
- 關閉之前已經引導的客戶端或服務器
- 總結
- 引導
- Bootstrap 類型
- 引導客戶端和無連接協議
- 引導服務器
- 從 Channel 引導客戶端
- 在一個引導中添加多個 ChannelHandler
- 使用Netty 的 ChannelOption 和屬性
- 關閉之前已經引導的客戶端或服務器
- 總結
- NETTY BY EXAMPLE
- 單元測試
- 總覽
- 測試 ChannelHandler
- 測試異常處理
- 總結
- WebSocket
- WebSocket 程序示例
- 添加 WebSocket 支持
- 測試程序
- 總結
- SPDY
- SPDY 背景
- 示例程序
- 實現
- 啟動 SpdyServer 并測試
- 總結
- 通過 UDP 廣播事件
- UDP 基礎
- UDP 廣播
- UDP 示例
- EventLog 的 POJO
- 寫廣播器
- 寫監視器
- 運行 LogEventBroadcaster 和 LogEventMonitor
- 總結
- 高級主題
- 實現自定義的編解碼器
- 編解碼器的范圍
- 實現 Memcached 編解碼器
- 了解 Memcached 二進制協議
- Netty 編碼器和解碼器
- 測試編解碼器
- EventLoop 和線程模型
- 線程模型的總覽
- EventLoop
- EventLoop
- I/O EventLoop/Thread 分配細節
- 總結
- 用例1:Droplr Firebase 和 Urban Airship
- 用例2:Facebook 和 Twitter