1.BIO方式適用於鏈接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發有侷限性,JDK1.4之前是惟一的選擇,好處是編碼實現方式簡單,且也容易理解。2.NIO方式適用於鏈接數目多且鏈接比較短的架構,好比聊天服務器,彈幕系統等,相比BIO編碼較複雜,JDK1.4之後開始支持。3.AIO方式適用於鏈接數據多且鏈接較長的場景,好比相冊服務器等,編程較複雜,JDK1.7纔開始支持。目前好像並未獲得普遍使用。java
Java BIO是傳統的Java io編程,相關的類和接口在http://java.io包中編程
Java BIO:同步阻塞,一個鏈接爲一個線程,鏈接一個客戶端就須要啓動一個線程進行處理,若是鏈接未斷開且未作任何事,會形成沒必要要的開銷。能夠經過線程池優化。windows
Java BIO:適用於鏈接數目較小且相對固定的架構,對服務器的要求比較高,對併發有侷限性。JDK1.4之前惟一的選擇,簡單易理解。api
1.服務器啓動ServerSoket。2.客戶端啓動Socket與服務器通訊,默認狀況下服務器須要對每一個客戶端創建一個線程與之通訊。3.客戶端發出請求與服務器通訊。4.若是請求成功,客戶端會等待請求結束後繼續執行。數組
實例要求:緩存
1.使用NIO編寫服務端,監聽8888端口號,當有客戶端鏈接時,啓動一個線程與之通訊。2.使用線程池改進,能夠鏈接多個客戶端。服務器
package com.crazy.io.bio;import java.io.IOException;import java.io.InputStream;import java.net.ServerSocket;import java.net.Socket;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/*** @author shiyi* @version 1.0* @date 2020-7-7 23:48*/public class BIOServer { public static void main(String[] args) throws IOException { /** * 1.建立一個線程池 * 若是有客戶端鏈接了,就建立一個線程與之通訊。 */ ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); // 建立ServerSocket ServerSocket serverSocket = new ServerSocket(6668); System.out.println("服務器啓動了"); while (true) { // 監聽,等待客戶端鏈接 final Socket socket = serverSocket.accept(); System.out.println("鏈接到了一個客戶端"); newCachedThreadPool.execute(new Runnable() { @Override public void run() { // 能夠和客戶端通信 handler(socket); } }); } } // 編寫一個handler方法,與客戶端通信 public static void handler(Socket socket) { // 經過socket獲取輸入流 try { System.out.println("線程信息 id=" + Thread.currentThread().getId() + "線程名字=" + Thread.currentThread().getName()); InputStream inputStream = socket.getInputStream(); byte[] bytes = new byte[1024]; // 循環的讀取客戶端發送的數據 while (true) { System.out.println("線程信息 id=" + Thread.currentThread().getId() + "線程名字=" + Thread.currentThread().getName()); int read = inputStream.read(bytes); if (read != -1) { // 輸出客戶端發送的數據 System.out.println(new String(bytes, 0, read)); } else { break; } } } catch (Exception e) { e.printStackTrace(); } finally { // 關閉和客戶端的鏈接 try { socket.close(); } catch (Exception e) { e.printStackTrace(); } } }}
客戶端網絡
package com.crazy.io.zerocopy;importjava.io.DataOutputStream;importjava.io.FileInputStream;importjava.io.InputStream;importjava.net.Socket; public class OldIOClient { public static void main(String[] args) throws Exception { Socket socket = new Socket("localhost", 6668); String fileName = "1.txt"; InputStream inputStream = new FileInputStream(fileName); DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream()); byte[] buffer = new byte[4096]; long readCount; long total = 0; long startTime = System.currentTimeMillis(); while ((readCount = inputStream.read(buffer)) >= 0) { total += readCount; dataOutputStream.write(buffer); } System.out.println("發送總字節數: " + total + ", 耗時: " + (System.currentTimeMillis() - startTime)); dataOutputStream.close(); socket.close(); inputStream.close(); }}
有小夥伴說,我就只想寫服務器,不想寫客戶端,能不能測試。能,我都給大家準備好了。可使用windows的命令行telnet命令來測試。多線程
telnet 127.0.0.1 6668
鏈接成功後經過按 Ctrl+] 符號進入發送數據界面架構
send HelloWorld
查看服務端收到的消息
完成了簡單的以BIO實現的客戶端與服務器之間的交互。
1.每一個請求都須要建立獨立的線程。
2.當併發量大時,須要建立大量線程,佔用系統資源。
3.鏈接創建後,若是當前線程暫時沒有數據可讀,則線程就阻塞在 Read 操做上,形成線程資源浪費
1.Java NIO全稱(java non-blocking io),從 JDK1.4 開始,Java 提供了一系列改進的輸入/輸出的新特性,被統稱爲 NIO(即 New IO),是同步非阻塞的。
2.NIO 相關類都被放在 java.nio 包及子包下,而且對原 http://java.io 包中的不少類進行了改寫。
3.NIO 有三大核心部分:Channel(通道),Buffer(緩衝區), Selector(選擇器) 。
4.NIO是 面向緩衝區。數據讀取到一個它稍後處理的緩衝區中,須要時可在緩衝區中先後移動,這就增長了處理過程當中的靈活性,使用它能夠提供非阻塞式的高伸縮性網絡。
5.Java NIO的非阻塞模式,使一個線程從某通道發送請求或者讀取數據,可是它僅能獲得目前可用的數據,若是目前沒有數據可用,就什麼都不獲取,會繼續保持線程阻塞,直至數據變的能夠讀取以前,該線程能夠繼續作其餘的事情。非阻塞寫也是如此,一個線程請求寫入一些數據到某通道,但不須要等待它徹底寫入,這個線程同時能夠去作別的事情。
6.通俗理解:NIO是能夠作到用一個線程來處理多個操做。假設有500個請求過來,根據實際狀況,能夠分配50或者100個線程來處理。而BIO可能須要建立500個線程來處理數據。
7.NIO的併發請求要遠遠大於BIO。
1.每一個channel 都會對應一個Buffer。
2.Selector 對應一個線程, 一個線程對應多個channel(鏈接)。
3.該圖反應了有三個channel 註冊到 該selector 程序。
4.程序切換到哪一個channel 是有事件決定的, Event 就是一個重要的概念。
5.Selector 會根據不一樣的事件,在各個通道上切換。
6.Buffer 就是一個內存塊 , 底層是有一個數組。
7.數據的讀取寫入是經過Buffer, 這個和BIO , BIO 中要麼是輸入流,或者是輸出流, 不能雙向,可是 NIO的Buffer 是能夠讀也能夠寫, 須要 flip 方法切換。
8.channel 是雙向的, 能夠返回底層操做系統的狀況, 好比Linux , 底層的操做系統通道就是雙向的。
緩衝區本質上是一個能夠讀寫數據的內存塊,能夠理解成是一個容器對象(含數
組),該對象提供了一組方法,能夠更輕鬆地使用內存塊,緩衝區對象內置了一
些機制,可以跟蹤和記錄緩衝區的狀態變化狀況。Channel 提供從文件、網絡讀
取數據的渠道,可是讀取或寫入的數據都必須經由 Buffer。
經常使用的ByteBuffer是一個抽象類,繼承Buffer,實現了Comparable接口。
1. mark:標記。
2. Position:位置,下一個要被讀或寫的元素的索引,每次讀寫緩衝區數據時都會改變改值,爲下次讀寫做準備。
3. Limit:表示緩衝區的當前終點,不能對緩衝區超過極限的位置進行讀寫操做。且極限是能夠修改的。
4. Capacity:容量,便可以容納的最大數據量;在緩衝區建立時被設定而且不能改變。
ByteBuffer中經常使用的方法:
// 緩衝區建立相關apipublic static ByteBuffer allocateDirect(int capacity)//建立直接緩衝區public static ByteBuffer allocate(int capacity)//設置緩衝區的初始容量public static ByteBuffer wrap(byte[] array)//把一個數組放到緩衝區中使用//構造初始化位置offset和上界length的緩衝區public static ByteBuffer wrap(byte[] array,int offset, int length)//緩存區存取相關APIpublic abstract byte get( );//從當前位置position上get,get以後,position會自動+1public abstract byte get (int index);//從絕對位置getpublic abstract ByteBuffer put (byte b);//從當前位置上添加,put以後,position會自動+1public abstract ByteBuffer put (int index, byte b);//從絕對位置上put
1.通道能夠同時進行讀寫,而流只能讀或者只能寫。
2.通道能夠實現異步讀寫數據。
3.通道能夠從緩衝讀數據,也能夠寫數據到緩衝。
Channel是一個接口。
1.經常使用的 Channel 類有:FileChannel、DatagramChannel、ServerSocketChannel 和 SocketChannel。
2.ServerSocketChanne 相似 ServerSocket 。
3.SocketChannel 相似 Socket。
實例要求:
1.把1.txt中的文件讀取到2.txt中。
public class NIOFileChannel03 { public static void main(String[] args) throws IOException { FileInputStream fileInputStream = newFileInputStream("1.txt"); FileChannel inputStreamChannel =fileInputStream.getChannel(); FileOutputStream fileOutputStream = newFileOutputStream("2.txt"); FileChannel outputStreamChannel =fileOutputStream.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate(10); while (true) { byteBuffer.clear(); int read = inputStreamChannel.read(byteBuffer); System.out.println("read =" + read); if (read == -1) { break; } byteBuffer.flip(); outputStreamChannel.write(byteBuffer); } fileInputStream.close(); inputStreamChannel.close(); outputStreamChannel.close(); fileOutputStream.close(); }}
1.ByteBuffer 支持類型化的put 和 get, put 放入的是什麼數據類型,get就應該使用相應的數據類型來取出,不然可能有 BufferUnderflowException 異常。
2.能夠將一個普通Buffer 轉成只讀Buffer。
3.NIO 還提供了 MappedByteBuffer, 可讓文件直接在內存(堆外的內存)中進行修改, 而如何同步到文件由NIO 來完成。
1.Java 的 NIO,用非阻塞的 IO 方式。能夠用一個線程,處理多個的客戶端鏈接,就會使用到Selector(選擇器)。2.Selector 可以檢測多個註冊的通道上是否有事件發生,若是有事件發生,便獲取事件而後針對每一個事件進行相應的處理。這樣就能夠只用一個單線程去管理多個通道,也就是管理多個鏈接和請求。3.只有在 鏈接/通道 真正有讀寫事件發生時,纔會進行讀寫,就大大地減小了系統開銷,而且沒必要爲每一個鏈接都建立一個線程,不用去維護多個線程。4.避免了多線程之間的上下文切換致使的開銷。
1.線程從某客戶端 Socket 通道進行讀寫數據時,若沒有數據可用時,該線程能夠進行其餘任務。
2.線程一般將非阻塞 IO 的空閒時間用於在其餘通道上執行 IO 操做,因此單獨的線程能夠管理多個輸入和輸出通道。
3.因爲讀寫操做都是非阻塞的,這就能夠充分提高 IO 線程的運行效率,避免因爲頻繁 I/O 阻塞致使的線程掛起。
4.一個 I/O 線程能夠併發處理 多個客戶端鏈接和讀寫操做,這從根本上解決了傳統同步阻塞 I/O 一個鏈接一個線程模型,架構的性能、彈性伸縮能力和可靠性都獲得了極大的提高。
5.客戶端鏈接時,會經過ServerSocketChannel 獲得 SocketChannel Selector 進行監聽 select 方法, 返回有事件發生的通道的個數。
6.將socketChannel註冊到Selector上, register(Selector sel, int ops), 一個selector上能夠註冊多個SocketChannel。
7.註冊後返回一個 SelectionKey, 會和該Selector 關聯(集合) 進一步獲得各個 SelectionKey。
8.在經過 SelectionKey 反向獲取 SocketChannel , 方法 channel() 能夠經過 獲得的 channel , 完成業務處理。
1.編寫一個 NIO 入門案例,實現服務器端和客戶端之間的數據簡單通信(非阻塞)。
package com.crazy.io.nio.buffer; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; /** * @author shiyi * @version 1.0 * @date 2020-7-8 22:32 */ public class NIOServer { public static void main(String[] args) throws IOException { // 建立serverSocketChannel ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 建立selector Selector selector = Selector.open(); // 綁定一個端口6666.在服務器監聽 serverSocketChannel.socket().bind(new InetSocketAddress(6666)); // 設置爲非阻塞 serverSocketChannel.configureBlocking(false); // 把serverSocketChannel註冊到selector 關心事件爲OP_ACCEPT serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 循環等待客戶端鏈接 while (true) { // 一秒沒有事件發生,沒有事件發生 if (selector.select(1000) == 0) { System.out.println("等待了一秒,無鏈接"); continue; } // 若是返回的大於0,拿到selectionkey集合 // 若是大於0,表示已回去到關注的事件 // 經過selectionkeys反向獲取通道 Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectionKeys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); // 根據key對應的通道發生的事件作相應處理 // 有新的客戶端來鏈接了 if (key.isAcceptable()) { SocketChannel socketChannel = serverSocketChannel.accept(); // 將socketChannel設置爲非阻塞 socketChannel.configureBlocking(false); System.out.println("客戶端鏈接成功======"); // 註冊到selector上, 關注事件爲讀,給socketChannel關聯一個Buffer socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024)); } if (key.isReadable()) { // 經過key 反向獲取對應的channel SocketChannel channel = (SocketChannel) key.channel(); // 獲取到管理的Buffer ByteBuffer buffer = (ByteBuffer) key.attachment(); channel.read(buffer); System.out.println("form客戶端" + new String(buffer.array())); } keyIterator.remove(); } } } }
客戶端
package com.crazy.io.nio.buffer;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SocketChannel;/** * @author shiyi * @version 1.0 * @date 2020-7-8 23:00 */public class NIOClient { public static void main(String[] args) throws IOException { // 等到通道 SocketChannel socketChannel = SocketChannel.open(); // 設置非阻塞 socketChannel.configureBlocking(false); InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666); // 鏈接服務器 if (!socketChannel.connect(inetSocketAddress)) { while (!socketChannel.finishConnect()) { System.out.println("沒有鏈接上,能夠作其它事情"); } } // 鏈接成功 String hello = "失憶老幺"; ByteBuffer buffer = ByteBuffer.wrap(hello.getBytes()); // 發送數據,將buffer數據寫到channel socketChannel.write(buffer); System.in.read(); }}
1.目前 AIO 尚未普遍應用。實際我也不懂。
原做者:失憶老幺
原文連接: BIO,NIO,AIO_失憶老幺-CSDN博客
原出處:CSDN博客
侵刪