Netty由淺入深(二)

Netty核心功能

下文內容大部分來自:https://github.com/waylau/essential-netty-in-action

爲了能方便閱讀下面內容,請了解下面相關知識,類圖的關聯關係如何用箭頭表示:

具體請參考:https://www.cnblogs.com/ylq1990/p/8473041.html這篇文章

 

1.傳輸

Transport API 的核心是 Channel 接口,用於所有的出站操作,見下圖

這幅圖包含了實現和組合關係圖,表示Channel在內部組合了ChannelPipeline,ChannelConfig

通過下面截圖可以具體知悉:

 

如上圖所示,每個 Channel 都會分配一個 ChannelPipeline 和ChannelConfig。ChannelConfig 負責設置並存儲 Channel 的配置,並允許在運行期間更新它們。傳輸一般有特定的配置設置,可能實現了 ChannelConfig. 的子類型。

ChannelPipeline 容納了使用的 ChannelHandler 實例,這些ChannelHandler 將處理通道傳遞的“入站”和“出站”數據以及事件。ChannelHandler 的實現允許你改變數據狀態和傳輸數據。

現在我們可以使用 ChannelHandler 做下面一些事情:

  • 傳輸數據時,將數據從一種格式轉換到另一種格式
  • 異常通知
  • Channel 變爲 active(活動) 或 inactive(非活動) 時獲得通知* Channel 被註冊或註銷時從 EventLoop 中獲得通知
  • 通知用戶特定事件

Intercepting Filter(攔截過濾器)

ChannelPipeline 實現了常用的 Intercepting Filter(攔截過濾器)設計模式。UNIX管道是另一例子:命令鏈接在一起,一個命令的輸出連接到 的下一行中的輸入。

你還可以在運行時根據需要添加 ChannelHandler 實例到ChannelPipeline 或從 ChannelPipeline 中刪除,這能幫助我們構建高度靈活的 Netty 程序。例如,你可以支持 STARTTLS 協議,只需通過加入適當的 ChannelHandler(這裏是 SslHandler)到的ChannelPipeline 中,當被請求這個協議時。

此外,訪問指定的 ChannelPipeline 和 ChannelConfig,你能在Channel 自身上進行操作。Channel 提供了很多方法,如下列表:

Table 4.1 Channel main methods

方法名稱 描述
eventLoop() 返回分配給Channel的EventLoop
pipeline() 返回分配給Channel的ChannelPipeline
isActive() 返回Channel是否激活,已激活說明與遠程連接對等
localAddress() 返回已綁定的本地SocketAddress
remoteAddress() 返回已綁定的遠程SocketAddress
write() 寫數據到遠程客戶端,數據通過ChannelPipeline傳輸過去
flush() 刷新先前的數據
writeAndFlush(...) 一個方便的方法用戶調用write(...)而後調用 flush()

後面會越來越熟悉這些方法,現在只需要記住我們的操作都是在相同的接口上運行,Netty 的高靈活性讓你可以以不同的傳輸實現進行重構。

寫數據到遠程已連接客戶端可以調用Channel.write()方法,如下代碼:

Listing 4.5 Writing to a channel

Channel channel = ...; // 獲取channel的引用
ByteBuf buf = Unpooled.copiedBuffer("your data", CharsetUtil.UTF_8);            //1
ChannelFuture cf = channel.writeAndFlush(buf); //2

cf.addListener(new ChannelFutureListener() {    //3
    @Override
    public void operationComplete(ChannelFuture future) {
        if (future.isSuccess()) {                //4
            System.out.println("Write successful");
        } else {
            System.err.println("Write error");    //5
            future.cause().printStackTrace();
        }
    }
});

1.創建 ByteBuf 保存寫的數據

2.寫數據,並刷新

3.添加 ChannelFutureListener 即可寫操作完成後收到通知,

4.寫操作沒有錯誤完成

5.寫操作完成時出現錯誤

Channel 是線程安全(thread-safe)的,它可以被多個不同的線程安全的操作,在多線程環境下,所有的方法都是安全的。正因爲 Channel 是安全的,我們存儲對Channel的引用,並在學習的時候使用它寫入數據到遠程已連接的客戶端,使用多線程也是如此。下面的代碼是一個簡單的多線程例子:

Listing 4.6 Using the channel from many threads

final Channel channel = ...; // 獲取channel的引用
final ByteBuf buf = Unpooled.copiedBuffer("your data",
        CharsetUtil.UTF_8).retain();    //1
Runnable writer = new Runnable() {        //2
    @Override
    public void run() {
        channel.writeAndFlush(buf.duplicate());
    }
};
Executor executor = Executors.newCachedThreadPool();//3

//寫進一個線程
executor.execute(writer);        //4

//寫進另外一個線程
executor.execute(writer);        //5

1.創建一個 ByteBuf 保存寫的數據

2.創建 Runnable 用於寫數據到 channel

3.獲取 Executor 的引用使用線程來執行任務

4.手寫一個任務,在一個線程中執行

+

5.手寫另一個任務,在另一個線程中執行

2.Buffer

Buffer API

主要包括

  • ByteBuf
  • ByteBufHolder

Netty 使用 reference-counting(引用計數)來判斷何時可以釋放 ByteBuf 或 ByteBufHolder 和其他相關資源,從而可以利用池和其他技巧來提高性能和降低內存的消耗。這一點上不需要開發人員做任何事情,但是在開發 Netty 應用程序時,尤其是使用 ByteBuf 和 ByteBufHolder 時,你應該儘可能早地釋放池資源。 Netty 緩衝 API 提供了幾個優勢:

+

  • 可以自定義緩衝類型
  • 通過一個內置的複合緩衝類型實現零拷貝(Netty零拷貝和零拷貝的概念請參考:https://www.cnblogs.com/xys1228/p/6088805.html
  • 擴展性好,比如 StringBuilder
  • 不需要調用 flip() 來切換讀/寫模式
  • 讀取和寫入索引分開
  • 方法鏈
  • 引用計數
  • Pooling(池)

ByteBuf - 字節數據的容器

因爲所有的網絡通信最終都是基於底層的字節流傳輸,因此一個高效、方便、易用的數據接口是必要的,而 Netty 的 ByteBuf 滿足這些需求。

ByteBuf 是一個很好的經過優化的數據容器,我們可以將字節數據有效的添加到 ByteBuf 中或從 ByteBuf 中獲取數據。爲了便於操作,ByteBuf 提供了兩個索引:一個用於讀,一個用於寫。我們可以按順序的讀取數據,也可以通過調整讀取數據的索引或者直接將讀取位置索引作爲參數傳遞給get方法來重複讀取數據。

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

 

ByteBufHolder

我們經常遇到需要另外存儲除有效的實際數據各種屬性值。 HTTP 響應是一個很好的例子;與內容一起的字節的還有狀態碼, cookies,等。

Netty 提供 ByteBufHolder 處理這種常見的情況。 ByteBufHolder 還提供對於 Netty 的高級功能,如緩衝池,其中保存實際數據的 ByteBuf 可以從池中借用,如果需要還可以自動釋放。

ByteBufHolder 有那麼幾個方法。到底層的這些支持接入數據和引用計數。表5.7所示的方法(忽略了那些從繼承 ReferenceCounted 的方法)。

Table 5.7 ByteBufHolder operations

名稱 描述
data() 返回 ByteBuf 保存的數據
copy() 製作一個 ByteBufHolder 的拷貝,但不共享其數據(所以數據也是拷貝).

如果你想實現一個“消息對象”有效負載存儲在 ByteBuf,使用ByteBufHolder 是一個好主意。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章