Java網(wǎng)絡(luò)IO模型詳解(BIO、NIO、AIO)
簡介
Java 支持三種網(wǎng)絡(luò) IO 模型:BIO、NIO、AIO。
- Java BIO 是同步阻塞模型,一個連接對應(yīng)一個線程,客戶端有連接請求時服務(wù)端就啟動一個線程,即使這個連接不做任何事情也會占用線程資源。
- Java NIO 是同步非阻塞模型,一個線程可以處理多個連接,客戶端連接請求會注冊到多路復(fù)用器(Selector),多路復(fù)用器檢測到連接有 IO 時間就會處理。
- Java AIO 是異步非阻塞模型,AIO 引入了異步通道的概念,讀寫異步通道會立刻返回,讀寫的數(shù)據(jù)由 Future 或 CompletionHandler 進(jìn)一步處理。
BIO 適用于連接數(shù)少的場景,程序編寫比較簡單,對服務(wù)器的資源要求比較高,JDK1.4之前的唯一選擇。NIO 適用于連接數(shù)多的場景,例如聊天服務(wù)器、服務(wù)器間通訊等,程序編寫比較復(fù)雜,JDK1.4開始支持。AIO 也適用于連接數(shù)多的場景,但更加偏向于異步操作多的場景。
Java BIO
模型示例
客戶端代碼示例
import java.io.*; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketAddress; public class BIOClient { public static void main(String[] args) { new BIOClient().start("localhost", 6666); } public void start(String host, int port) { // 初始化 socket Socket socket = new Socket(); try { // 設(shè)置 socket 連接 SocketAddress remote = new InetSocketAddress(host, port); socket.setSoTimeout(5000); socket.connect(remote); // 發(fā)送數(shù)據(jù) PrintWriter writer = getWriter(socket); writer.write("hello server"); writer.flush(); // // 發(fā)起請求 // PrintWriter writer = getWriter(socket); // writer.write(compositeRequest(host)); // writer.flush(); // // // 讀取響應(yīng) // String msg; // BufferedReader reader = getReader(socket); // while ((msg = reader.readLine()) != null) { // System.out.println(msg); // } } catch (IOException e) { e.printStackTrace(); } finally { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } private BufferedReader getReader(Socket socket) throws IOException { InputStream in = socket.getInputStream(); return new BufferedReader(new InputStreamReader(in)); } private PrintWriter getWriter(Socket socket) throws IOException { OutputStream out = socket.getOutputStream(); return new PrintWriter(new OutputStreamWriter(out)); } private String compositeRequest(String host) { return "GET / HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "User-Agent: curl/7.43.0\r\n" + "Accept: */*\r\n\r\n"; } }
服務(wù)端代碼示例
import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class BIOServer { public static void main(String[] args) throws Exception { // 創(chuàng)建一個線程池 ExecutorService pool = Executors.newCachedThreadPool(); // 創(chuàng)建 ServerSocket ServerSocket serverSocket = new ServerSocket(6666); while (true) { // 等待客戶端連接 final Socket socket = serverSocket.accept(); // 接收到一個客戶端連接 放入線程池進(jìn)行處理 pool.execute(() -> process(socket)); } } static void process(Socket socket) { try { byte[] bytes = new byte[1024]; // 通過 socket 獲取輸入流 InputStream inputStream = socket.getInputStream(); // 循環(huán)讀取客戶端發(fā)送的數(shù)據(jù) while (true) { // 沒有數(shù)據(jù)的時候這里會阻塞等待 int read = inputStream.read(bytes); if (read == -1) break; // 輸出客戶端發(fā)送的數(shù)據(jù) System.out.println(new String(bytes, 0, read)); } } catch (Exception e) { e.printStackTrace(); } finally { try { socket.close(); } catch (Exception e) { e.printStackTrace(); } } } }
Java NIO
NIO 采用 Reactor 模式,屬于 IO 多路復(fù)用模型,可以用一個線程處理多個請求。NIO 有三大核心模塊,通道(Channel)、緩沖區(qū)(Buffer)、選擇器(Selector)。NIO 的非阻塞模式,使主線程在未發(fā)生數(shù)據(jù)讀寫事件時無需阻塞,可以繼續(xù)做其他事情,這就大大增強(qiáng)了服務(wù)器的并發(fā)處理能力。
模型示例
Selector 對應(yīng)一個線程,一個 Selector 可以對應(yīng)多個 Channel,一個 Channel 對應(yīng)一個 Buffer。程序切換到哪個 Channel 是由事件決定的,Selector 會根據(jù)不同的事件切換不同的 Channel。下圖描述了 Channel、Buffer 和 Selector 的關(guān)系。
MappedByteBuffer 簡介
NIO 提供的 MappedByteBuffer 支持支持在內(nèi)存(堆外內(nèi)存)中修改文件,可以減少一次數(shù)據(jù)拷貝。文件同步的部分,由 NIO 自己完成。
代碼示例
import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; /** * 說明 1.MappedByteBuffer 可讓文件直接在內(nèi)存(堆外內(nèi)存)修改,操作系統(tǒng)不需要拷貝一次 */ public class MappedByteBufferTest { public static void main(String[] args) throws Exception { RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt", "rw"); //獲取對應(yīng)的通道 FileChannel channel = randomAccessFile.getChannel(); /** * 參數(shù) 1:FileChannel.MapMode.READ_WRITE 使用的讀寫模式 * 參數(shù) 2:0:可以直接修改的起始位置 * 參數(shù) 3:5: 是映射到內(nèi)存的大?。ú皇撬饕恢茫?,即將 1.txt 的多少個字節(jié)映射到內(nèi)存 * 可以直接修改的范圍就是 0-5 * 實際類型 DirectByteBuffer */ MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5); mappedByteBuffer.put(0, (byte) 'H'); mappedByteBuffer.put(3, (byte) '9'); mappedByteBuffer.put(5, (byte) 'Y');//IndexOutOfBoundsException randomAccessFile.close(); System.out.println("修改成功~~"); } }
NIO 編程代碼原理分析圖
關(guān)于 NIO 非阻塞網(wǎng)絡(luò)編程相關(guān)的(Selector、SelectionKey、ServerScoketChannel 和 SocketChannel)關(guān)系梳理圖
服務(wù)端代碼示例
可以結(jié)合上面的原理圖觀察代碼實現(xiàn)細(xì)節(jié)
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.Iterator; public class GroupChatServer { // 定義屬性 private Selector selector; private ServerSocketChannel listenChannel; private static final int PORT = 6667; // 構(gòu)造器執(zhí)行初始化工作 public GroupChatServer() { try { // 得到選擇器 selector = Selector.open(); // 監(jiān)聽端口的主線程 listenChannel = ServerSocketChannel.open(); // 綁定端口 listenChannel.socket().bind(new InetSocketAddress(PORT)); // 設(shè)置非阻塞模式 listenChannel.configureBlocking(false); // 將該 listenChannel 注冊到 selector listenChannel.register(selector, SelectionKey.OP_ACCEPT); } catch (IOException e) { e.printStackTrace(); } } public void listen() { try { // 循環(huán)處理 while (true) { int count = selector.select(); // 有事件處理 if (count > 0) { // 遍歷得到 selectionKey 集合 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { // 取出 selectionKey SelectionKey key = iterator.next(); // 監(jiān)聽到 accept if (key.isAcceptable()) { SocketChannel sc = listenChannel.accept(); sc.configureBlocking(false); // 將該 sc 注冊到 selector sc.register(selector, SelectionKey.OP_READ); // 提示 System.out.println(sc.getRemoteAddress() + " 上線 "); } if (key.isReadable()) {// 通道發(fā)送read事件,即通道是可讀的狀態(tài) // 處理讀(專門寫方法..) readData(key); } // 當(dāng)前的 key 刪除,防止重復(fù)處理 iterator.remove(); } } else { System.out.println("等待...."); } } } catch (Exception e) { e.printStackTrace(); } finally { // 發(fā)生異常處理.... } } // 讀取客戶端消息 public void readData(SelectionKey key) { SocketChannel channel = null; try { // 得到 channel channel = (SocketChannel) key.channel(); // 創(chuàng)建 buffer ByteBuffer buffer = ByteBuffer.allocate(1024); int count = channel.read(buffer);// NIO這里不會阻塞 因為事件觸發(fā)時必然已經(jīng)有數(shù)據(jù)了 所以叫非阻塞IO // 根據(jù) count 的值做處理 if (count > 0) { // 把緩存區(qū)的數(shù)據(jù)轉(zhuǎn)成字符串 String msg = new String(buffer.array()); // 輸出該消息 System.out.println("form客戶端:" + msg); // 向其它的客戶端轉(zhuǎn)發(fā)消息(去掉自己),專門寫一個方法來處理 sendInfoToOtherClients(msg, channel); } } catch (IOException e) { try { System.out.println(channel.getRemoteAddress() + "離線了.."); // 取消注冊 key.cancel(); // 關(guān)閉通道 channel.close(); } catch (IOException e2) { e2.printStackTrace(); } } } // 轉(zhuǎn)發(fā)消息給其它客戶(通道) private void sendInfoToOtherClients(String msg, SocketChannel self) throws IOException { System.out.println("服務(wù)器轉(zhuǎn)發(fā)消息中..."); // 遍歷所有注冊到 selector 上的 SocketChannel,并排除 self for (SelectionKey key : selector.keys()) { // 通過 key 取出對應(yīng)的 SocketChannel Channel targetChannel = key.channel(); // 排除自己 if (targetChannel instanceof SocketChannel && targetChannel != self) { // 轉(zhuǎn)型 SocketChannel dest = (SocketChannel) targetChannel; // 將 msg 存儲到 buffer ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes()); // 將 buffer 的數(shù)據(jù)寫入通道 dest.write(buffer); } } } public static void main(String[] args) { // 創(chuàng)建服務(wù)器對象 GroupChatServer groupChatServer = new GroupChatServer(); groupChatServer.listen(); } }
Java AIO
AIO 是異步非阻塞的,引入了異步通道的概念,采用 Proactor 模式,操作系統(tǒng)完成數(shù)據(jù)拷貝操作后才會通知服務(wù)端線程。AIO 本質(zhì)上還是 IO 多路復(fù)用模型,與 NIO 比起來,AIO 只是在非阻塞的前提下增加了異步功能,具體則體現(xiàn)在代碼編寫以及數(shù)據(jù)傳輸兩個層面。
- 從代碼編寫角度來說,原來的同步方法會阻塞等待接口返回,而現(xiàn)在可以異步等待返回結(jié)果。
- 從數(shù)據(jù)傳輸角度來說,每個請求都需要傳輸數(shù)據(jù),NIO 雖然是非阻塞的,但是事件到達(dá)后,NIO 需要自己把數(shù)據(jù)從內(nèi)核空間復(fù)制到用戶空間。AIO 引入異步邏輯后,事件到達(dá)后系統(tǒng)不會立刻通知服務(wù)端線程,而是會自己把數(shù)據(jù)從內(nèi)核空間復(fù)制到用戶空間,完成這個操作后,才會通知服務(wù)端線程去處理。
AIO 的使用場景還是比較少,現(xiàn)在大部分開源框架中應(yīng)該還是以使用 NIO 為主,AIO 在性能方面的提升還是比較有限,主要的變化還是增加了異步功能。
如何理解 Reactor 和 Proactor 的區(qū)別?
Reactor 可以理解為「來了事件操作系統(tǒng)通知應(yīng)用進(jìn)程,讓應(yīng)用進(jìn)程來處理」,而Proactor 可以理解為「來了事件操作系統(tǒng)來處理,處理完再通知應(yīng)用進(jìn)程」。
總結(jié)
到此這篇關(guān)于Java網(wǎng)絡(luò)IO模型(BIO、NIO、AIO)的文章就介紹到這了,更多相關(guān)Java網(wǎng)絡(luò)BIO、NIO、AIO內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring容器的創(chuàng)建過程之如何注冊BeanPostProcessor詳解
關(guān)于BeanPostProcessor 各位一定不陌生,今天整理的這篇文章總結(jié)了如何注冊BeanPostProcessor,文中有非常詳細(xì)的圖文示例,需要的朋友可以參考下2021-06-06基于Spring AOP proxyTargetClass的行為表現(xiàn)總結(jié)
這篇文章主要介紹了Spring AOP proxyTargetClass的行為表現(xiàn)總結(jié),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08簡化API提升開發(fā)效率RestTemplate與HttpClient?OkHttp關(guān)系詳解
這篇文章主要為大家介紹了簡化API,提升開發(fā)效率,RestTemplate與HttpClient?OkHttp關(guān)系介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10Java實戰(zhàn)之實現(xiàn)文件資料上傳并生成縮略圖
這篇文章主要介紹了通過Java實現(xiàn)文件資料的上傳并生成一個縮略圖,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Java有一定的幫助,感興趣的小伙伴可以了解一下2021-12-12Java的Hibernate框架中的組合映射學(xué)習(xí)教程
組合映射即是指主對象和子對象關(guān)聯(lián)且擁有相同的生命周期的映射關(guān)系,這里我們將舉一些數(shù)據(jù)操作的實例,來講解Java的Hibernate框架中的組合映射學(xué)習(xí)教程2016-07-07