Netty 新手入門 ( 一 ) Netty 與 NIO

前言

最近因爲工作需要使用到Netty方面的技術點,而以前也對這方面沒有太多的關注過,所以邊學習邊總結,也給自己留個資料,以便以後查看起來方便。

Java NIO

再說Netty 之前先簡單瞭解下Java 的NIO,因爲Netty也是爲了簡化我們的編碼對Java NIO進行了一系列的封裝。

Java NIO 從java領域講就是 NEW I/O ,另一種就是Non-blocking I/O。它是一種同步非阻塞的I/O模型,也是I/O多路複用的基礎。

在NIO中有三個非常重要的概念,緩衝區(Buffer)、通道(Channel)、選擇器(Selector)

緩衝區(Buffer)

1、基礎概念

緩衝區本質上來說就是一個數組,在NIO中,所有的操作都是面向緩衝區的。讀取數據時,是從緩衝區讀取,寫入數據時也是寫入到緩衝區。所有NIO的數據操作都是操作的緩衝區,而面向流I/O系統中,數據是直接寫入到Stream對象中的。

在NIO API中所有的緩衝區類型都繼承Buffer,我們經常用到的就是ByteBuffer,NIO這邊對Java的基本類型都有實現。

2、基本原理

上面提到過緩衝區其實就是一個數組,其實是一個特殊的數組,它裏面內置了一些機制來幫助我們進行數據的讀寫,能夠跟蹤和記錄緩衝區的變化方便我們使用。

在緩衝區中有三個重要的屬性進行合作完成了緩存區內部狀態變化的跟蹤:

  • position: 指定下一個將要被寫入或者讀取的元素索引,它的值由get()/put()方法自動更新,在新創建一個Buffer對象時,position被初始化爲0。
  • limit: 指定還有多少數據需要取出(在從緩衝區寫入通道時),或者還有多少空間可以放入數據(在從通道讀入緩衝區時)
  • capacity:指定了可以存儲在緩衝區中的最大數據容量,實際上,它指定了底層數組的大小,或者至少是指定了准許我們使用的底層數組的容量.

以上三個屬性的之間的關係 :0<=position<=limit<=capacity。也就是說當我們創建一個容量爲10的ByteBuffer對象時,初始化的時候,position設置爲0,limit和capacity設置爲10,在以後使用ByteBuffer對象過程中,capacity的值不會再發生變化,而其他兩個將會隨着使用而變化。

簡單的寫個僞代碼:


//1、分配一個容量爲10的緩衝區
ByteBuffer buffer = ByteBuffer.allocate(10);

//2、然後我們可以執行操作將數據讀取到buffer
channel.read(buffer);
//3、讀取到buffer後我們需要對buffer 進行翻轉操作(重點)
buffer.flip();

//4、翻轉後,就可以從buffer 裏面進行數據讀取了
buffer.get();

//5、重置buffer
buffer.clear();

上面大體上分爲了五部分,下面簡單的瞭解下到底發生了什麼?
1、初始化buffer,分配空間,可以直觀的看到buffer三個參數的位置
image.png

2、從通道中讀取一些數據到緩衝區,這裏我們讀取了4個數據,可以看到position此時爲4,而limit爲10
image.png

3、當我們需要把數據從緩衝出輸出時,必須先調用flip()方法。此方法完成了兩件事:一是把limit值設置爲當前position的值。二是設置position值爲0。其實就是爲了告訴我們當前數據的頭和尾是什麼。

image.png

4、通過get()叢緩衝區讀取數據,這時position值會隨着數據讀取增加,而limit保持不變,但position不會超過limit的值。
image.png

5、在數據讀取完成後,我們可以使用clear()方法將buffer狀態進行初始化
image.png

3、緩衝區分配

在上面例子中我們可以看到分配一個緩衝區對象時,會調用方法allocate(10),相當於創建了一個指定大小的數組,幷包裝成緩衝區對象。
分配緩衝區一般有兩種方式:

//1、分配指定大小緩衝區
ByteBuffer buffer = ByteBuffer.allocate(10);
//2、包裝現有數組
byte[] arr = new byte[10];
ByteBuffer buffer2 = ByteBuffer.warp(arr);
4、子緩衝區

子緩衝區,就是在我們剛纔ByteBuffer.allocate(10)分配的空間內在劃出一塊區域獨立操作,調用slice() 可創建一個子緩衝區。子緩衝區和當前緩衝區是數據共享的,其實只是給其實一塊區域提供了一個視圖窗口。

5、只讀緩衝區

看名字就知道,這buffer只能讀取數據。我們可以調用asReadOnlyBuffer()方法,將我們的ByteBuffer 轉換爲只讀緩衝區,注意,這個方法不是真的將我們當前緩衝區轉換爲只讀了,而是返回來一個和當前緩衝區參數一樣的緩衝區,並和原緩衝區數據共享,只不過這個是隻讀的。如果原緩衝區內容發送改變,只讀緩衝區內容也會發生變化(簡單理解下就是增加了一個只讀引用)
僞代碼

ByteBuffer buffer = ByteBuffer.allocate(10);

//創建只讀緩衝區
ByteBuffer readonly = buffer.asReadOnlyBuffer();

6、直接緩衝區

都知道我們不管怎麼創建buffer其實都在JVM內部,在進行數據傳輸的時候都避免不了,從虛擬機內存拷貝到內核空間的複製操作。而直接緩衝區,就是省略了從用戶空間到內核空間拷貝的過程,使用了一個物理內存映射文件來直接對數據進行操作。在代碼中使用是很簡單的,需調用allocateDirect()方法,而不是allocate()方法。

image.png

7、內存映射(mmap)

內存映射是一種讀和寫文件數據的方法,比常規基於流或通道的I/O快的多。簡單點說就是將文件的磁盤扇區映射到進程的虛擬內存空間的過程。

選擇器

與傳統Client/Server模式不同,NIO中非阻塞I/O採用了基於Reactor模式的工作方式,I/O調用不會被阻塞,而是註冊感興趣的特定I/O事件,如可讀數據到達、新的套接字連接等,在發生特定事件時,系統再通知我們。NIO中實現非阻塞I/O的核心對象是Selector,Selector是註冊各種I/O事件的地方,而且當那些事件發生時,就是Seleetor告訴我們所發生的事件

image.png

從圖中可以看出,當有讀或寫等任何註冊的事件發生時,可以從Selector中獲得相應的SelectionKey,同時從SelectionKey中可以找到發生的事件和該事件所發生的具體的SelectableChannel,以獲得客戶端發送過來的數據

使用NIO中非阻塞I/O編寫服務器處理程序,大體上可以分爲下面三個步驟。

  • (1)向Selector對象註冊事件。
  • (2)從Selector中獲取事件。
  • (3)根據不同的事件進行相應的處理。

通道

通道就是一個對象,它可以幫我們進行數據讀取和寫入,當然所有數據都是通過操作Buffer對象來處理的。

1、NIO讀取寫入數據

讀取數據分爲三步:

  • (1)從FileInputStream獲取Channel。
  • (2)創建Buffer。
  • (3)將數據從Channel讀取到Buffer中。

來看下僞代碼

//獲取文件Channel
FileChannel fc = out.getChannel();
//分配緩衝區
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffe.put(1);
buffe.put(2);

buffer.flip();

//將數據寫入文件
fc.write(buffer);
out.close();
2、多路複用I/O

什麼是多路複用器?
1、解決IO 狀態的問題 不解決你的IO 讀寫數據的問題。
2、解決用更少的系統調用,一下詢問所有的IO狀態,不是一次次的詢問IO 的狀態(與內核詢問),減少了用戶態和內核態切換的過程。

目前流行的多路複用I/O的實現主要包括四種:select、poll、epoll、kqueue。如下表所示是它們的一些重要特性的比較。

多路複用器 Select poll 是阻塞的,其實就是把文件描述符放在jvm 的內存中開闢了一個數組,如果是epoll 就是調用了 epoll_create 放到了內核空間。

qrcode_for_gh_13314ac27929_258.jpg

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