《Netty實戰》-學習筆記1

LudwigWuuu發表於2019-01-06

《Netty實戰》-學習筆記1

Chapter 3 Netty的元件及設計

  • Channel:Socket,對應一個連線。

    • 提供基本的 I/O 操作:如 bind()connect()read()write() 等,就是對 Java 中實現的 Socket 進行一定層次的封裝。
  • EventLoop:控制流、多執行緒處理、併發。

    • 在一個 EventLoopGroup 中包含一個或多個 EventLoop
    • 一個 EventLoop 在它的生命週期內只和一個 Thread 繫結。
    • 所有 EventLoop 處理的 I/O 事件都在它專有的 Thread 上被處理。
    • 一個 Channel 在它的生命週期內只註冊於一個 EventLoop
    • 一個 EventLoop 可能會被分配給一個或多個 Channel
  • ChannelFuture:非同步通知。

    • 在 Netty 中所有操作的是非同步的,呼叫時不會立即返回結果,但是返回一個 Future 。能夠在之後的某個時間點確定結果,對 Future 新增 Listener 註冊回撥,再操作完成後通知執行相應的回撥。
  • ChannelHandler:在 Channel 上發生事件的處理器。

    • 由網路事件觸發的處理器,實際上可以響應任何型別的動作。
    • ChannelInboundHandler:接收入站事件和資料。
    • ChannelOutboundHandler:資料出站處理器。
  • ChannelPipelineChannelHandler 鏈的容器。

    • ChannelPipeline 就是一些 ChannelHandler 的編排順序,每個 ChannelHandler 接收事件,執行所實現的處理邏輯,完畢後交給下個一個 ChannelHandler
    • 從客戶端角度來說,傳送資料到伺服器就是出站的。則通過 ChannelPipeline 的編排,在一定次序下呼叫 ChannelOutboundHandler 。響應的接收伺服器發來資料就是入站的,在一個 ChannelPipeline 按順序呼叫 ChannelInboundHandler
    • 如下圖:客戶端作業系統從網路上接收伺服器發來資料放入快取中,接下來,在 Netty 中從 ChannelPipeline 中從頭部開始按順序呼叫 ChannnelInboundHandler。從客戶端傳送資料到伺服器則是從 ChannelPipeline 的尾部開始執行,直到傳送到網路上。
      《Netty實戰》-學習筆記1
  • ChannelHandlerContext:代表了 ChannelHandlerChannelPipeline 之間的繫結。

    • 可以用於獲取底層Channel
  • 兩種傳送訊息的方式:

    • 直接寫入 Channel 中:訊息從 ChannelPipeline 的尾部開始流動。
    • 寫入到 ChannelHandler 相關聯的 ChannelHandlerContext 物件中: 訊息從 ChannelPipeline 的下一個ChannelHandler開始流動。

Chapter 4 傳輸API

Channel

《Netty實戰》-學習筆記1
每個 Channel 都會被分配一個 ChannelPipelineChannelConfig

  • ChannelConfig:對應 Channel 的配置設定,支援熱更新。
  • ChannelPipeline: 持有所有將應用於入站和出站資料以及事件的 ChannelHandler 例項,這些 ChannelHandler 實現了應用程式用於處理狀態變化以及資料處理的邏輯。

內建傳輸

名稱 描述
NIO io.netty.channel.socket.nio 使用 java.nio.channels 包作為基礎——基於選擇器(select)
Epoll io.netty.channel.epoll 由 JNI 驅動的 epoll() 和非阻塞 IO。這個傳輸支援只有在Linux上可用的多種特性,如 SO_REUSEPORT,比NIO 傳輸更快,而且是完全非阻塞的
OIO io.netty.channel.socket.oio 使用 java.net 包作為基礎——使用阻塞流
Local io.netty.channel.local 可以在 VM 內部通過管道進行通訊的本地傳輸
Embedded io.netty.channel.embedded Embedded 傳輸,允許使用 ChannelHandler 而又不需要一個真正的基於網路的傳輸。這在測試 ChannelHandler 實現時非常有用
  • NIO 基於選擇器,每個 Channel 在選擇器(Selector)上註冊,當 Selector 呼叫 select() 的時候是阻塞的,直到已經註冊的 Channel 中有相應的事件發生。
事件 描述
OP_ACCEPT 請求在接受新連線並建立 Channel 時獲得通知
OP_CONNECT 請求在建立一個連線時獲得通知
OP_READ 請求當資料已經就緒,可以從 Channel 中讀取時獲得通知
OP_WRITE 請求當可以向 Channel 中寫更多的資料時獲得通知。這處理了套接字緩衝區被完全填滿時的情況,這種情況通常發生在資料的傳送速度比遠端節點可處理的速度更快的時候

處理模型:

《Netty實戰》-學習筆記1

  • Epoll NIO 是基於 Select 的,那麼 Epoll 如其名,則是基於 Linux 的 epoll 。好處不言而喻,提供比舊的 select 和 poll 更好的效能。 對應的則是 EpollEventLoopGroupEpollServerSocketChannel

  • OIO 建立在 java.net 包下,不是非同步的。能夠通過設定 SO_TIMEOUT 來在單執行緒中處理多個連線。

  • Embedded Embedded 提供了可以將一組 ChannelHandler 作為幫助器嵌入到其他的 ChannelHandler 內部。這樣可以擴充套件一個 ChannelHandler 的功能,但又不需要修改其內部程式碼。

Chapter 5 ByteBuf

ByteBuf工作原理

維護了兩個不同的索引:readerIndexwriterIndex ,一個用於讀取,一個用於寫入。當從 ByteBuf 讀取時,readerIndex 遞增。同樣當從 ByteBuf 寫入時, writerIndex 遞增。 如果 readerIndex 遞增到和 writerIndex 相同的值,那麼到達“可讀取資料的末尾”,這時再進行讀取則會像讀取超出陣列末尾的資料一樣,丟擲 IndexOutOfBoundsException。 另外,名稱以 read 或者 write 開頭的 ByteBuf 方法會推進相應的索引值,而以 set 或者 get 開頭的方法則不會(這樣只是在相應的索引值上進行操作)。

使用模式

在弄清楚 ByteBuf 的使用模式之前,應該理解 Java 中的直接緩衝區和非直接緩衝區:

  • 直接緩衝區:緩衝區建立在實體記憶體中,可以提高效率。

  • 非直接緩衝區:緩衝區建立在 JVM 的記憶體中。

  • 堆緩衝區(backing 模式) ByteBuf 資料儲存在 JVM 的堆空間(非直接緩衝區)中。這種模式也被稱為“支撐陣列(backing array)”,能夠在沒有使用池化的的情況下提供快速的分配和釋放。

  • 直接緩衝區(direct 模式) 通過本地呼叫分配記憶體,直接分配的是實體記憶體。

  • 複合緩衝區 為多個 ByteBuf 提供一個聚合檢視。

    • CompositeByteBuf:能夠提供將多個緩衝區(每個 ByteBuf 可以是 backing 模式或者 direct 模式)表示為單個合併緩衝區的虛擬表示。

位元組級操作

  1. 隨機訪問索引 ByteBuf 的索引是從 0 開始到 capacity() - 1。 獲取每個位元組的方法則是:byte getByte(int i); 這樣倒不會改變 readerIndexwriterIndex 的值。 當然可以通過:ByteBuf readerIndex(int index)ByteBuf writerIndex(int index)來手動移動讀取/寫入索引值。
  2. 順序訪問索引 有三個區域:
    《Netty實戰》-學習筆記1
  3. 可丟棄位元組 通過呼叫discardReadBytes()方法,可以丟棄已經被讀過的位元組以回收空間。是的,呼叫discardReadBytes()可以確保可寫分段的最大化,但是有可能導致記憶體複製(可讀位元組會移動到緩衝區的開始位置)。同樣的可丟棄位元組空間也只是相應的更改了 writerIndex 的值,並不會保證空間中值的擦寫。
  4. 可讀位元組 ByteBuf 的可讀位元組分段(CONTENT)儲存了實際資料。任何名稱以 read 或者 skip 開頭的操作都將檢索或者跳過位於當前 readerIndex 的資料,並且將它增加已讀位元組數。
  5. 可寫位元組 可寫位元組分段是指一個擁有未定義內容的、寫入就緒的記憶體區域。
  6. 索引管理 在 JDK 中 InputStream定義了mark(int readlimit)reset()方法,來標記流中指定位置,以及重置。在 ByteBuf 中也有相應的方法來標記和重置 ByteBuf 中的 readerIndexwriterIndex
    • markReaderIndex()
    • markWriterIndex()
    • resetReaderIndex()
    • resetWriterIndex() 還有clear()方法只是重置索引,且不會複製記憶體。
  7. 查詢操作 最簡單的,可以使用indexOf()方法。 或者使用ByteProcessor類。
  8. 派生緩衝區 提供了專門的方法來呈現器內容的檢視:
    • duplicate()
    • slice()
    • slice(int , int)
    • Unpooled.unmodifiableBuffer(...)
    • order(ByteOrder)
    • readSlice(int) 以上每個方法都會返回一個新的ByteBuf例項,具有自己的讀索引、寫索引和標記索引。但是內部儲存是共享的。所以如果修改了它的內容,也同時修改了其對應的源例項。 如果要深拷貝獲得真實副本,使用copy()或者copy(int ,int)函式。
  9. 讀/寫操作 兩種類別的讀/寫操作:
    • get()set()操作,從給定的索引值開始,且保持索引值不變。
    • read()write()操作,從給定的索引開始,且會根據以及訪問過的位元組數堆索引進行調整。 get()方法:
      《Netty實戰》-學習筆記1
      set()方法:
      《Netty實戰》-學習筆記1
      read()方法:
      《Netty實戰》-學習筆記1
      write()方法:
      《Netty實戰》-學習筆記1
  10. 更多的操作
    《Netty實戰》-學習筆記1

ByteBuf分配

  1. 按需分配:ByteBufAllocator介面 ByteBufAllocator的方法:
    《Netty實戰》-學習筆記1
    可以通過Channel或者ChannelHandlerContext獲取到一個ByteBufAllocator的引用。 Netty提供了兩種ByteBufAllocator的實現:
  • PooledByteBufAllocator:池化了ByteBuf的例項以提高效能並且最大限度的減少記憶體碎片。
  • UnpooledByteBufAllocator:不池化ByteBuf例項,但是每次被呼叫時會返回一個新的例項。
  1. Unpooled緩衝區 無法獲取到ByteBufAllocator的引用時,有一個簡單的Unpooled工具類,提供靜態的輔助方法來建立未池化的ByteBuf例項。
    《Netty實戰》-學習筆記1
  2. ByteBufUtil類 這個類提供了用於操作ByteBuf的靜態輔助方法。

相關文章