1. 程式人生 > >深入Java網路程式設計與NIO(二)

深入Java網路程式設計與NIO(二)

Java NIO 與 Netty NIO

NIO的特性/NIO與IO區別:

  • 1)IO是面向流的,NIO是面向緩衝區的;
  • 2)IO流是阻塞的,NIO流是不阻塞的;
  • 3)NIO有選擇器,而IO沒有。

讀資料和寫資料方式:

  • 從通道進行資料讀取 :建立一個緩衝區,然後請求通道讀取資料。
  • 從通道進行資料寫入 :建立一個緩衝區,填充資料,並要求通道寫入資料。

NIO三大核心元件:Channels 、Buffers 、Selectors
Netty對應也有幾大元件:

Channel: 它代表一個到實體(如一個硬體裝置、一個檔案、一個網路套接字或者一個能夠執行一個或者多個不同的I/O操作的程式元件)的開放連線,如讀操作和寫操作;將會為每個channel分配一個EventLoop
EventLoop: 控制流、多執行緒處理、併發
EventLoopGroup
ChannelHandler: 為了響應特定事件而被執行的回撥: 每一個handler都在一個HanderPipeline中
ChannelFuture: 非同步通知

netty

1.Buffers

buffer
其實核心是最後的 ByteBuffer,前面的一大串類只是包裝了一下它而已,我們使用最多的通常也是 ByteBuffer。
MappedByteBuffer 用於實現直接記憶體對映mmp。

Buffer 和陣列差不多,它有 position、limit、capacity 幾個重要屬性。put() 一下資料、flip() 切換到讀模式、然後用 get() 獲取資料、clear() 一下清空資料、重新回到 put() 寫入資料。

ByteBuffer

NIO的資料傳輸是基於緩衝區的,ByteBuffer正是NIO資料傳輸中所使用的緩衝區抽象。ByteBuffer支援在 堆外分配記憶體DirectBuffer

,並且嘗試避免在執行I/O操作中的多餘複製。通過JNI呼叫來在堆外分配記憶體(呼叫malloc()函式在JVM堆外分配記憶體),這主要是為了避免額外的緩衝區複製操作。
一般的I/O操作都需要進行系統呼叫,這樣會先切換到核心態,核心態要先從檔案讀取資料到它的緩衝區,只有等資料準備完畢後,才會從核心態把資料寫到使用者態,所謂的阻塞IO其實就是說的在等待資料準備好的這段時間內進行阻塞。如果想要避免這個額外的核心操作,可以通過使用mmap(虛擬記憶體對映)的方式來讓使用者態直接操作檔案。

Netty 中的 ByteBuf

網路傳輸的基本單位是位元組,在Java NIO中提供了ByteBuffer作為位元組緩衝區容器,但該類的API使用起來不太方便,所以Netty實現了ByteBuf作為其替代品,下面是使用ByteBuf的優點:

相比ByteBuffer使用起來更加簡單。
通過內建的複合緩衝區型別實現了透明的zero-copy。
容量可以按需增長。
讀和寫使用了不同的索引指標。
支援鏈式呼叫。
支援引用計數與池化。
可以被使用者自定義的緩衝區型別擴充套件。

2.Channels

channel

FileChannel:檔案通道,用於檔案的讀和寫
DatagramChannel:用於 UDP 連線的接收和傳送
SocketChannel:把它理解為 TCP 連線通道,簡單理解就是 TCP 客戶端
ServerSocketChannel:TCP 對應的服務端,用於監聽某個埠進來的請求

SocketChannel。它可以看作是 socket 的一個完善類,除了提供 Socket 的相關功能外,還提供了許多其他特性,如後面要講到的向選擇器註冊的功能。

Socket相關的類圖:
socket

它類似於檔案描述符,簡單地來說它代表了一個實體(如一個硬體裝置、檔案、Socket或者一個能夠執行一個或多個不同的I/O操作的程式元件)。你可以從一個Channel中讀取資料到緩衝區,也可以將一個緩衝區中的資料寫入到Channel。

Netty中的 Channel

  • ChannelHandler
    ChannelHandler充當了處理入站和出站資料的應用程式邏輯的容器,該類是基於事件驅動的,它會響應相關的事件然後去呼叫其關聯的回撥函式,例如當一個新的連線被建立時,ChannelHandler的channelActive()方法將會被呼叫。

Netty中到處都充滿了 非同步與事件驅動,而回調函式正是用於響應事件之後的操作。由於非同步會直接返回一個結果,所以Netty提供了ChannelFuture(實現了java.util.concurrent.Future)來作為非同步呼叫返回的佔位符,真正的結果會在未來的某個時刻完成,到時候就可以通過ChannelFuture對其進行訪問,每個Netty的出站I/O操作都將會返回一個ChannelFuture。

3.Selectors

Selector 建立在非阻塞的基礎之上,大家經常聽到的多路複用世界中指的就是它,用於實現一個執行緒管理多個 Channel。

對於 Selector,我們還需要非常熟悉以下幾個方法:

1. select()

呼叫此方法,會將上次 select 之後的準備好的 channel 對應的 SelectionKey 複製到 selected set 中。如果沒有任何通道準備好,這個方法會阻塞,直到至少有一個通道準備好。

2. electNow()

功能和 select 一樣,區別在於如果沒有準備好的通道,那麼此方法會立即返回 0。

3. select(long timeout)

看了前面兩個,這個應該很好理解了,如果沒有通道準備好,此方法會等待一會

4. wakeup()

這個方法是用來喚醒等待在 select() 和 select(timeout) 上的執行緒的。如果 wakeup() 先被呼叫,此時沒有執行緒在 select 上阻塞,那麼之後的一個 select() 或 select(timeout) 會立即返回,而不會阻塞,當然,它只會作用一次。