# AIO、BIO、NIO,NETTY框架 I/O Models - Blocking I/O, BIO - Non-Blocking I/O, NIO - Asynchronous I/O, AIO ## I/O模型的演變 引用來源: https://alibaba-cloud.medium.com/essential-technologies-for-java-developers-i-o-and-netty-ec765676fd21 ### 傳統I/O模型 對於傳統的 I/O 操作,用戶端連接到伺服器,它們通過以下過程相互通信:客戶端發送請求,伺服器讀取、解碼、計算和編碼請求,然後向用戶端發送回應。伺服器為每個用戶端連接創建一個線程和一個通道,然後處理後續請求(在 BIO 模式下)。 在此模式下,當客戶端數量增加時,對連接請求的回應會急劇減少,並且佔用過多的線程,從而浪費資源。在這種情況下,由於線程數量有限而出現瓶頸。雖然線程池可用於優化,但仍然存在許多問題。例如,當線程池中的所有線程都在處理請求時,它們無法回應來自其他客戶端的連接請求。每個用戶端仍然需要來自專用伺服器線程的服務。即使用戶端在給定時間沒有請求,線程也會被阻止且無法釋放。為了解決這個問題,提出了事件驅動的反應堆模型。 ![](https://hackmd.io/_uploads/BJ_znorAn.png) ### Reactor Model ## 同步、非同步、阻塞、非阻塞 Synchronous (同步)、Asynchronous (非同步) 在這裡意義為在執行IO動作時, 是由jvm處理或是交由OS處理來區分. 若為jvm處理則為Synchronous, 否則為Asynchronous. Block(阻塞)、Non-Block(非阻塞) 依據"進行IO操作時是否需要等待"作為區分. 若操作IO時當下的Thread需要等待則為Block, 否則為Non-Block. ![](https://hackmd.io/_uploads/rk5rYsBC2.png) ![](https://hackmd.io/_uploads/SJxjqoHRh.png) ## BIO ![](https://hackmd.io/_uploads/HJFnKjBC3.png) ## BIO 實戰 ```java import java.io.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ConcurrentBlockingIOExample { public static void main(String[] args) { int numThreads = 5; // 想要建立的線程數量 ExecutorService executorService = Executors.newFixedThreadPool(numThreads); for (int i = 0; i < numThreads; i++) { executorService.execute(new IOOperation()); } executorService.shutdown(); } static class IOOperation implements Runnable { @Override public void run() { String fileName = "example.txt"; try { // 建立IO new io // 開始讀取文件內容 io.read // 關閉資源 io.close } catch (IOException e) { e.printStackTrace(); } } } } ``` ## NIO實戰 通道可以实现双向读写,比如说用RandomAccessFile类获取文件读写,调用RandomAccessFile.getChannel()方法,获取的就是读写双向的通道 ```java import java.io.IOException; import java.io.RandomAccessFile; public class NonBlockingFileIOExample { public static void main(String[] args) { String fileName = "example.txt"; Thread readerThread = new Thread(() -> { try { // 打開文件並進行非阻塞讀取 RandomAccessFile file = new RandomAccessFile(fileName, "r"); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = file.read(buffer)) != -1) { String data = new String(buffer, 0, bytesRead); System.out.println("讀取的內容: " + data); } file.close(); } catch (IOException e) { e.printStackTrace(); } }); readerThread.start(); } } ``` ## AIO實戰 ```java public class AioServer { public static void main(String[] args) throws IOException { System.out.println(Thread.currentThread().getName() + " AioServer start"); AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open() .bind(new InetSocketAddress("127.0.0.1", 8080)); serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() { @Override public void completed(AsynchronousSocketChannel clientChannel, Void attachment) { System.out.println(Thread.currentThread().getName() + " client is connected"); ByteBuffer buffer = ByteBuffer.allocate(1024); clientChannel.read(buffer, buffer, new ClientHandler()); } @Override public void failed(Throwable exc, Void attachment) { System.out.println("accept fail"); } }); System.in.read(); } } public class ClientHandler implements CompletionHandler<Integer, ByteBuffer> { @Override public void completed(Integer result, ByteBuffer buffer) { buffer.flip(); byte [] data = new byte[buffer.remaining()]; buffer.get(data); System.out.println(Thread.currentThread().getName() + " received:" + new String(data, StandardCharsets.UTF_8)); } @Override public void failed(Throwable exc, ByteBuffer buffer) { } } ``` ## NIO ### NIO主要就是為了解決BIO進行IO操作時Blocak所造成效率不佳的問題。 NIO是基於Reactor(事件驅動), <font color="red">也就是有IO事件發生時再來處理就好.</font> 因此導入了一個Selector概念, 將連線的發生、IO資料輸入/輸出都視為"事件(Event)", 在此只要操作Selector取出事件作相對應的事情即可. 這概念可以想像成底層將這些"事件(Event)"都塞到了一個queue裡, 然後操作這個Selector不斷的從這個queue取出這些event進行處理即可. 但因為所有的事情都視為event, 所以所有client的連線事件都會混在一起被select出來處理, 因此需要比較複雜的程式判斷才不至於混淆。 操作流程大致上是: ``` 1.操作selector取出event 2-1.若是connect事件需要完成TCP三方交握, 完成後會取得一個與這個Client專屬的Channel, 再將此Channel註冊回Selector, Selector將會監聽此Channel Input事件的發生。 2-2.若是Input事件, 則可以透過ByteBuffer將Input的內容取出, 若當下ByteBuffer不足以取出所有Input內容, 那麼下次select一樣有會此次Channel的Input事件被取出, 直到程式將Input全部取完。 2-3.上述的操作皆可以使用與select同個Thread做事, 或交由Thread Pool去處理. 若使用同個來Thread處理Input事件, 那概念就會像是Node.js單線程操作一樣, 遇到IO時一樣交由底層所控制的Thread去執行, 當下的Thread可以回來繼續處理業務邏輯. 3.回頭繼續取出事件 ``` NIO在IO操作時引入了兩個新個概念,"Channel"與"ByteBuffer". 1. "Channel": 對應BIO的InputStream, OutputStream. BIO的I/O Stream是單向操作, 而NIO的Channel是雙向操作, 讀/寫均透過此物件. 2. "ByteBuffer": NIO在進行IO操作時均要使用ByteBuffer給予Channel. 概念就是要output的內容時會先塞到ByteBuffer, 再丟給Channel去output(底層將調用別的Thread進行output動作); 而Input事件發生時, 底層會先將內容暫存, 我們程式一樣透過ByteBuffer取用. 這是我們程式與底層IO間的重要橋樑, 讓我們可以以非同步狀態操作IO, 進而讓Thread不再因為等待IO而偷懶. ![](https://hackmd.io/_uploads/ryCYjsHA2.png) 海底撈很好喫,但是經常要排隊。我們就以生活中的這個例子進行講解。 A 顧客去喫海底撈,就這樣乾坐着等了一小時,然後纔開始喫火鍋。(BIO) B 顧客去喫海底撈,他一看要等挺久,於是去逛商場,每次逛一會就跑回來看有沒有排到他。於是他最後既購了物,又喫上海底撈了。(NIO) C 顧客去喫海底撈,由於他是高級會員,所以店長說,你去商場隨便玩吧,等下有位置,我立馬打電話給你。於是 C 顧客不用幹坐着等,也不用每過一會兒就跑回來看有沒有等到,最後也喫上了海底撈(AIO) 哪種方式更有效率呢?是不是一目瞭然呢? ## AIO ![](https://hackmd.io/_uploads/SkroCjrC3.png) AIO(非同步 I/O)是 Java 中的一種非同步 I/O 模型,它在 Java 7 及以後的版本中引入,旨在改善傳統的阻塞 I/O(BIO)和非阻塞 I/O(NIO)模型的一些限制。 AIO主要與NIO最大的差別是IO是否由jvm處理. NIO在處理IO的時候, 仍然是由jvm處理, 也就是NIO底層仍然會調用Thread進行處理; 而AIO在處理IO時是委託OS進行處理, 不占用jvm資源. 而IO委託OS進行處理, 就意味著OS需要支援IO非同步操作 AIO的操作流程完全基於callback function(也是listener的概念), 在操作接受連線、讀取input等動作時, 是準備一個callback給channel, 在底層準備好這些事情時將會呼叫這些callback相對應的method, 就不需要像NIO一樣需要占用一個Thread不斷select事件來處理. 而在操作output動作時, 則是跟NIO一樣準備好ByteBuffer丟給Channel進行output, 並同時給定一個callback, 底層將在完成output事情後呼叫對應的method. 工作原理: 1. 在 AIO 模型中,與傳統 I/O 不同,不會阻塞線程等待數據可用或數據寫入。相反,AIO 使用回調機制,當數據可用時,操作系統會通知應用程序,應用程序負責處理數據。 2. AIO 主要通過兩種非同步事件來完成:讀取完成事件和寫入完成事件。應用程序通過註冊回調函數來處理這些事件。 3. 當數據可讀取時,操作系統會調用註冊的讀取完成回調函數來處理數據。同樣,當可以進行寫入操作時,操作系統會調用寫入完成回調函數。 特點: 1. 非阻塞: AIO 模型是非阻塞的,它允許應用程序發起 I/O 操作後繼續執行其他任務,不會被 I/O 操作阻塞。 2. 高並發: AIO 適用於高並發的網絡應用,因為它可以使用較少的線程處理大量的連接。 3. 事件驅動: AIO 采用事件驅動的方式,應用程序需要註冊回調函數來處理非同步事件,這種模型適用於事件驅動的應用,如高性能的網絡伺服器。 4. 適應瞬時高負載: AIO 適合處理瞬時高負載的情況,因為它可以有效地處理大量的並發連接,而不會導致線程資源耗盡。 總結:AIO 是 Java 中的一種非同步 I/O 模型,適用於高並發、事件驅動的網絡應用,它通過非同步操作和回調機制提高了 I/O 操作的效率和可伸縮性。 ### 其他參考文獻 http://www.52im.net/thread-4283-1-1.html ## Netty Netty 是一個高性能、異步事件驅動的網路應用框架,基於 Java 的 NIO(New I/O)技術。它旨在簡化網路應用程序的開發,提供了強大的抽象和易用的 API,同時具有出色的性能和可擴展性。 * Netty特點 1. 異步和事件驅動:Netty 使用異步和事件驅動的模型,使得開發者可以編寫非阻塞的、高效率的網路應用。 2. 高性能:Netty 的性能出色,具有低延遲和高吞吐量。它是許多高並發網路應用的首選框架。 3. 模塊化和可擴展:Netty 是一個模塊化的框架,你可以根據需求選擇性地使用它的模塊。它還支持自定義的編解碼器和處理器,以滿足特定需求。 4. 多協議支援:Netty 提供對多種協議的支援,包括 HTTP、WebSocket、SSL/TLS、Google Protobuf、zlib/gzip 壓縮等。 5. 零拷貝:Netty 支持零拷貝技術,可以提高數據傳輸的效率,減少數據複製的開銷。 6. 安全性:Netty 支持安全的網路通信,包括 SSL/TLS 加密。 ![](https://hackmd.io/_uploads/HyAQciH03.png) **綠色的部分**Core核心模塊,包括零拷貝、API庫、可擴展的事件模型。 **橙色的部分**Protocol Support協議支持,包括Http協議、webSocket、SSL(安全套接字協議)、谷歌Protobuf協議、zlib/gzip壓縮與解壓縮、Large File Transfer大文件傳輸等等。 **紅色的部分**Transport Services傳輸服務,包括Socket、Datagram、Http Tunnel等等。 ## Java IO模型的演進 1. BIO(Blocking I/O): 初始版本,最早的Java版本使用,JDK 1.0 到 JDK 1.3 主要提供了同步阻塞的 I/O 模型,也就是 BIO(Blocking I/O)。在BIO模型中,每個I/O操作都是阻塞的,這意味著當執行I/O操作時,執行緒會被阻塞,直到I/O操作完成。這導致了伺服器的可擴展性問題,因為每個連接需要一個獨立的執行緒。 2. NIO(Non-blocking I/O): 隨著Java 1.4的引入,NIO被引入以解決BIO的可擴展性問題。NIO引入了通道(Channel)和緩衝區(Buffer)的概念,並提供了選擇器(Selector)來監聽多個通道上的事件。這使得一個單線程可以管理多個連接,提高了伺服器的性能和擴展性。 3. AIO(Asynchronous I/O): AIO是Java 7的一部分,主要用於處理非常大的並發連接。AIO引入了異步I/O操作的概念,它們不會阻塞執行緒,因此一個執行緒可以同時處理多個I/O操作。這對高性能伺服器應用程序非常有用,例如高度並發的網絡服務。 ## BIO BIO程式設計簡單流程 伺服器端啟動一個ServerSocket; 使用者端啟動Socket對伺服器進行通 信,預設情況下伺服器端需要對每 個客戶 建立一個執行緒與之通訊; 使用者端發出請求後, 先諮詢伺服器 是否有執行緒響應,如果沒有則會等 待,或者被拒絕; 如果有響應,使用者端執行緒會等待請 求結束後,在繼續執行; 1.等待連線 2.取得連線, 分派該連線給其他Thread處理 3.回頭繼續等待連線 BIO (Blocking I/O):有一排水壺在燒開水,BIO的工作模式就是,叫一個線程停留在一個水壺那,直到這個水壺燒開,才去處理下一個水壺。但是實際上線程在等待水壺燒開的時間段什麼都沒有做。 原文網址:https://kknews.cc/comic/j5mpo8q.html