【JAVA NIO】java NIO
本文是博主深入學習Netty前的一些鋪墊,之前只是使用Netty,用的很粗暴,導包,上網找個DEMO就直接用,對Netty中的元件瞭解並不深入。
於是再此總結下基礎,並對一些核心元件作如下記錄:
1. 概述
java NIO核心的API:Channel,Buffer 和 Selector
所有的 IO 在NIO 中都從一個Channel 開始。Channel 有點象流。 資料可以從Channel讀到Buffer中,也可以從Buffer 寫到Channel中。
使用Selector,得向Selector註冊Channel,然後呼叫它的select()方法。這個方法會一直阻塞到某個註冊的通道有事件就緒。一旦這個方法返回,執行緒就可以處理這些事件,事件的例子有如新連線進來,資料接收等。
2.詳述
摘抄以下詳細說明,摘抄地址:https://blog.csdn.net/anxpp/article/details/51512200
JDK 1.4中的java.nio.*包中引入新的Java I/O庫,其目的是提高速度。實際上,“舊”的I/O包已經使用NIO重新實現過,即使我們不顯式的使用NIO程式設計,也能從中受益。速度的提高在檔案I/O和網路I/O中都可能會發生,但本文只討論後者。
2.1、簡介
NIO我們一般認為是New I/O(也是官方的叫法),因為它是相對於老的I/O類庫新增的(其實在JDK 1.4中就已經被引入了,但這個名詞還會繼續用很久,即使它們在現在看來已經是“舊”的了,所以也提示我們在命名時,需要好好考慮),做了很大的改變。但民間跟多人稱之為Non-block I/O,即非阻塞I/O,因為這樣叫,更能體現它的特點。而下文中的NIO,不是指整個新的I/O庫,而是非阻塞I/O。
NIO提供了與傳統BIO模型中的Socket和ServerSocket相對應的SocketChannel和ServerSocketChannel兩種不同的套接字通道實現。
新增的著兩種通道都支援阻塞和非阻塞兩種模式。
阻塞模式使用就像傳統中的支援一樣,比較簡單,但是效能和可靠性都不好;非阻塞模式正好與之相反。
對於低負載、低併發的應用程式,可以使用同步阻塞I/O來提升開發速率和更好的維護性;對於高負載、高併發的(網路)應用,應使用NIO的非阻塞模式來開發。
下面會先對基礎知識進行介紹。
2.2、緩衝區 Buffer
Buffer是一個物件,包含一些要寫入或者讀出的資料。
在NIO庫中,所有資料都是用緩衝區處理的。在讀取資料時,它是直接讀到緩衝區中的;在寫入資料時,也是寫入到緩衝區中。任何時候訪問NIO中的資料,都是通過緩衝區進行操作。
緩衝區實際上是一個數組,並提供了對資料結構化訪問以及維護讀寫位置等資訊。
具體的快取區有這些:ByteBuffe、CharBuffer、 ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。他們實現了相同的介面:Buffer。
2.3、通道 Channel
我們對資料的讀取和寫入要通過Channel,它就像水管一樣,是一個通道。通道不同於流的地方就是通道是雙向的,可以用於讀、寫和同時讀寫操作。
底層的作業系統的通道一般都是全雙工的,所以全雙工的Channel比流能更好的對映底層作業系統的API。
Channel主要分兩大類:
SelectableChannel:使用者網路讀寫
FileChannel:用於檔案操作
後面程式碼會涉及的ServerSocketChannel和SocketChannel都是SelectableChannel的子類。
2.4、多路複用器 Selector
Selector是Java NIO 程式設計的基礎。
Selector提供選擇已經就緒的任務的能力:Selector會不斷輪詢註冊在其上的Channel,如果某個Channel上面發生讀或者寫事件,這個Channel就處於就緒狀態,會被Selector輪詢出來,然後通過SelectionKey可以獲取就緒Channel的集合,進行後續的I/O操作。
一個Selector可以同時輪詢多個Channel,因為JDK使用了epoll()代替傳統的select實現,所以沒有最大連線控制代碼1024/2048的限制。所以,只需要一個執行緒負責Selector的輪詢,就可以接入成千上萬的客戶端。
3.Java NIO建立的過程
以下是java 提供的API 建立NIO server處理類的過程,各個API之間處理過程如下,可以看出比傳統BIO Socket程式設計要多出很多物件,建立過程也比較複雜。
博主簡化了關鍵程式碼用於方便閱讀,完整程式碼連結:https://blog.csdn.net/the_fool_/article/details/80700558
//建立選擇器
Selector selector = Selector.open();
//開啟ServerSocketChannel,監聽客戶端連線
ServerSocketChannel serverChannel = ServerSocketChannel.open();
//如果為 true,則此通道將被置於阻塞模式;如果為 false,則此通道將被置於非阻塞模式
serverChannel.configureBlocking(false);
//繫結埠 backlog設為1024
serverChannel.socket().bind(new InetSocketAddress(port), 1024);
//監聽客戶端連線請求
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
//無論是否有讀寫事件發生,selector每隔1s被喚醒一次
selector.select(1000);
//返回已此通道已準備就緒的鍵集
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
SelectionKey key = null;
//處理所有的
while (it.hasNext()) {
key = it.next();
it.remove();
//處理Key中的資訊
if (key.isValid()) {
//處理新接入的請求訊息
if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
//通過ServerSocketChannel的accept建立SocketChannel例項
//完成該操作意味著完成TCP三次握手,TCP物理鏈路正式建立
SocketChannel sc = ssc.accept();
//設定為非阻塞的
sc.configureBlocking(false);
//註冊為讀
sc.register(selector, SelectionKey.OP_READ);
}
//讀訊息
if (key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
//建立ByteBuffer,並開闢一個1M的緩衝區
ByteBuffer buffer = ByteBuffer.allocate(1024);
//讀取請求碼流,返回讀取到的位元組數
int readBytes = sc.read(buffer);
//讀取到位元組,對位元組進行編解碼
if (readBytes > 0) {
//將緩衝區當前的limit設定為position=0,用於後續對緩衝區的讀取操作
buffer.flip();
//根據緩衝區可讀位元組數建立位元組陣列
byte[] bytes = new byte[buffer.remaining()];
//將緩衝區可讀位元組陣列複製到新建的陣列中
buffer.get(bytes);
String expression = new String(bytes, "UTF-8");
System.out.println("SERVER GET MSSG:" + expression);
//處理資料
String result = "server got ";
//傳送應答訊息
responseToClient(sc, result);
}
//鏈路已經關閉,釋放資源
}
}
}
}