NIO网络模型
1. NIO簡介
NIO:Non-blocking I/O 或 New I/O
- 從特性出發(fā):非阻塞IO
- 從年齡出發(fā):新IO,在原始IO之后出現(xiàn)的
誕生:從 JDK 1.4 引入的全新的輸入輸出標(biāo)準(zhǔn)庫,重新設(shè)計的一套IO標(biāo)準(zhǔn)
職務(wù):高并發(fā)網(wǎng)絡(luò)服務(wù)器支持崗。作為原始IO的補充,為了應(yīng)對高性能高并發(fā)的應(yīng)用場景。
2. 編程模型
模型:對事物共性的抽象(個人理解:用一個統(tǒng)一的方法解決一系列相似的問題)
編程模型:對編程共性的抽象(個人理解:用一個統(tǒng)一的編程方法解決一系列相似的問題)
2.1 BIO網(wǎng)絡(luò)模型
BIO:Blocking I/O,阻塞IO。
傳統(tǒng)的 BIO 通過socket.read()讀取服務(wù)端的數(shù)據(jù),如果TCP RecvBuffer里沒有數(shù)據(jù),函數(shù)會一直阻塞,直到收到數(shù)據(jù),返回讀到的數(shù)據(jù)。
當(dāng)有很多客戶端訪問服務(wù)端,但客戶端未發(fā)送數(shù)據(jù),服務(wù)端線程處于線程阻塞,等待客戶端響應(yīng),這樣可能會導(dǎo)致服務(wù)端崩潰。
在大量并發(fā)的情況下,接入的客戶端過多,會出現(xiàn)問題。
BIO網(wǎng)絡(luò)模型的缺點:
- 阻塞式I/O模型。(服務(wù)端必須等待客戶端的再次請求)
- 彈性伸縮能力差。(客戶端與服務(wù)端的線程數(shù)時 1:1 的關(guān)系)
- 多線程耗資源(線程創(chuàng)建、銷毀,或大量線程處于服務(wù)器端都會使得服務(wù)端的CPU調(diào)度資源受到影響)
2.2 NIO網(wǎng)絡(luò)模型
selector:負(fù)責(zé)管理與客戶端建立的多個連接,負(fù)責(zé)監(jiān)聽注冊到上面的一些事件,如有新連接接入、當(dāng)前連接上有可讀消息或可寫消息。一旦事件被其監(jiān)聽到,就會調(diào)用對應(yīng)的事件處理器來完成對事件的響應(yīng)。
主要功能:接收所有與客戶端socket的連接,并且監(jiān)聽它們關(guān)心的事件,當(dāng)這個事件發(fā)生之后,就會調(diào)用相應(yīng)的事件處理器來處理這個事件。
2.3 NIO網(wǎng)絡(luò)模型改進(jìn)
- 非阻塞式I/O模型。(服務(wù)器端提供一個單線程的Selector來統(tǒng)一管理客戶端socket接入的所有連接,并負(fù)責(zé)每個連接所關(guān)心的事件)
- 彈性伸縮能力強。(服務(wù)器端不再是通過多個線程來處理請求,而是通過一個線程來處理所有請求,對應(yīng)關(guān)系變?yōu)?N:1)
- 單線程節(jié)省資源。(線程頻繁創(chuàng)建、銷毀,線程之間的上下文切換等帶來的資源消耗得要緩解)
3. NIO網(wǎng)絡(luò)編程詳解
NIO核心類:
- Channel:通道
- Buffer:緩沖區(qū)
- Selector:選擇器 或 多路復(fù)用器
3.1 Channel簡介
3.1.1 Channe特性
(1)雙向性
? Channel是信息傳輸?shù)耐ǖ?#xff0c;是 JDK NIO中對輸入輸出方式的另一種抽象,可以類比BIO中的流的概念。但與流不同的是,流是單向傳輸,有InputStream()、OutputStream(),而Channel是雙向傳輸,即可讀,又可寫。
(2)非阻塞性
? Channel可以工作在非阻塞模式下,正是由于這個特性,構(gòu)成了NIO的基礎(chǔ)。
(3)操作唯一性
? 操作Channel的唯一方式是使用Buffer,通過Buffer操作Channel實現(xiàn)數(shù)據(jù)塊的讀寫。Buffer本質(zhì)上就是一個字節(jié)數(shù)組。
3.1.2 Channel核心實現(xiàn)類
- 文件類:FileChannel
- UDP類:DatagramChannel
- TCP類:ServerSocketChannel / SocketChannel
Socket回顧:
/********** 服務(wù)端 **********/// 1. 監(jiān)聽端口 ServerSocket serverSocket = new ServerSocket(8000); while (true) {// 2. 接收請求,建立連接Socket socket = serverSocket.accept();// 3. 數(shù)據(jù)交換new Thread(new BIOServerHandler(socket)).start(); } // 4. 關(guān)閉資源 serverSocket.close();/********** 客戶端 **********/// 1. 建立連接 Socket socket = new Socket("127.0.0.1", 8000); // 2. 獲取輸入輸出流 InputStream inputStream = socket.getInputStream(); OutputStream outStream = socket.getOutputStream();Channel使用:
// 代碼片段 1:服務(wù)器端通過服務(wù)器socket創(chuàng)建channel ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 代碼片段 2:服務(wù)器端綁定端口 serverSocketChannel.bind(new InetSocketAddress(8000)); // 代碼片段 3:服務(wù)器端監(jiān)聽客戶端連接,建立socketChannel連接 SocketChannel socketChannel = serverSocketChannel.accept(); // 代買片段 4:客戶端連接遠(yuǎn)程主機及端口 SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8000))3.2 Buffer簡介
? 提供唯一與Channel進(jìn)行交互的方式。
- 作用:讀寫Channel中的數(shù)據(jù)
- 本質(zhì):一塊可以從中讀取或?qū)懭氲膬?nèi)存區(qū)域。被NIO包裝成一個NIO Buffer對象,并提供一組便于操作內(nèi)存的方法。
3.2.1 Buffer屬性
(1)Capacity:容量。
標(biāo)明數(shù)組可以容納的最大字節(jié)長度,若超出容量,則需要將其清空后才能重新寫入。
(2)Position:位置
寫模式:表示當(dāng)前的位置,初始位置為0,最大值為 Capacity-1
讀模式:重置為0
(3)Limit:上限
寫模式:最多能往Buffer中寫的數(shù)據(jù)數(shù)量,此時等于Capacity
讀模式:此時等于寫模式下的Position值。
(4)Mark:標(biāo)記
標(biāo)記一個特定Position位置,可通過Buffer的Reset()方法恢復(fù)到標(biāo)記位置。
3.2.2 Buffer使用
// 初始化長度為10的byte類型Buffer ByteBuffer.allocate(10); // 向byteBuffer中寫入三個字節(jié) byteBuffer.put("abc".getBytes(Charset.forName("UTF-8"))); // 將byteBuffer從寫模式切換成讀模式 byteBuffer.flip(); // 從byteBuffer中讀取一個字節(jié) byteBuffer.get(); // 調(diào)用mark方法記錄下當(dāng)前position的位置 byteBuffer.mark(); // 先調(diào)用get方法讀取下一個字節(jié) byteBuffer.get() // 再調(diào)用reset()方法將position重置到mark位置 byteBuffer.reset() // 調(diào)用clear方法,將所有屬性重置 byteBuffer.clear()3.3 Selector簡介
Java NIO中能夠檢測1到多個NIO通道,并能夠知曉通道是否為諸如讀寫事件做好準(zhǔn)備的組件。由Selector就能通過一個單獨的線程來管理多個Channel,從而管理多個網(wǎng)絡(luò)連接。
3.3.1 Selector使用
// 代碼片段 1:創(chuàng)建Selector Selector selector = Selector.open(); // 代碼片段 2:將channel注冊到selector上,監(jiān)聽讀就緒事件 SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_READ); // 代碼片段 3:阻塞等待channel的就緒事件發(fā)生 int selectNum = selector.select(); // 代碼片段 4:獲取發(fā)生就緒事件的channel集合 Set<SelectionKey> selectedKeys = selector.selectedKeys();3.3.2 SelectionKey簡介
(1)四種就緒狀態(tài)常量
SelectionKey常量提供了四個可監(jiān)聽事件的靜態(tài)常量值。
- OP_CONNECT:連接就緒。連接操作,Client端支持的一種操作
- OP_ACCEPT:接收就緒。連接可接受操作,僅ServerSocketChannel支持
- OP_READ:讀就緒。
- OP_WRITE:寫就緒。
(2)可以獲取有價值的屬性
在調(diào)用Selector對象的SelectedKey方法時,會返回一個SelectionKey的集合。可以通過這個SelectionKey的集合獲取當(dāng)前的Channel、當(dāng)前Selector的對象、Channel已就緒事件集合和所關(guān)心事件集合。
總結(jié)