java中BIO、NIO、AIO都有啥區別
一、BIO(Blocking IO,也被稱作old IO)
同步阻塞模型,一個客戶端連接對應一個處理線程
對於每一個新的網絡連接都會分配給一個線程,每隔線程都獨立處理自己負責的輸入和輸出, 也被稱為Connection Per Thread模式
缺點:
1、IO代碼裡read操作是阻塞操作,如果連接不做數據讀寫操作會導致線程阻塞,浪費資源
2、如果線程很多,會導致服務器線程太多,壓力太大,比如C10K問題
所謂c10k問題,指的是服務器同時支持成千上萬個客戶端的問題,也就是concurrent 10 000 connection
應用場景: BIO 方式適用於連接數目比較小且固定的架構, 這種方式對服務器資源要求比較高, 但程序簡單易理解。
示例代碼如下:
Bio服務端
import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; /** * @Title:BIO的服務端 * @Author:wangchenggong * @Date 2021/4/13 9:41 * @Description * @Version */ public class SocketServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(9000); while (true){ System.out.println("等待連接..."); Socket clientSocket = serverSocket.accept(); System.out.println("客戶端"+clientSocket.getRemoteSocketAddress()+"連接瞭!"); handle(clientSocket); } } private static void handle(Socket clientSocket) throws IOException{ byte[] bytes = new byte[1024]; int read = clientSocket.getInputStream().read(bytes); System.out.println("read 客戶端"+clientSocket.getRemoteSocketAddress()+"數據完畢"); if(read != -1){ System.out.println("接收到客戶端的數據:" + new String(bytes, 0, read)); } clientSocket.getOutputStream().write("HelloClient".getBytes()); clientSocket.getOutputStream().flush(); } }
Bio客戶端
import java.io.IOException; import java.net.Socket; /** * @Title:BIO的客戶端 * @Author:wangchenggong * @Date 2021/4/13 9:49 * @Description * @Version */ public class SocketClient { public static void main(String[] args) throws IOException { Socket socket = new Socket("localhost", 9000); //向服務端發送數據 socket.getOutputStream().write("HelloServer".getBytes()); socket.getOutputStream().flush(); System.out.println("向服務端發送數據結束"); byte[] bytes = new byte[1024]; //接收服務端回傳的數據 socket.getInputStream().read(bytes); System.out.println("接收到服務端的數據:" + new String(bytes)); socket.close(); } }
二、NIO(Non Blocking IO,本意也作new IO)
同步非阻塞,服務器實現模式為 一個線程可以處理多個連接請求(連接),客戶端發送的連接請求都會註冊到多路復用器selector上,多路復用器輪詢到連接有IO請求就進行處理,是在JDK1.4開始引入的。
應用場景:NIO方式適合連接數目多且連接比較短(輕操作)的架構,比如聊天服務器、彈幕系統、服務器之間通訊,編程相對復雜。
NIO 有三大核心組件: Channel(通道), Buffer(緩沖區),Selector(多路復用器)
1.channel類似於流,每個channel對應一個buffer緩沖區,buffer底層就是個數組
2.channel 會註冊到selector上,由selector根據channel讀寫事件的發生將其交由某個空閑的線程處理
3.NIO的Buffer和Channel都是可讀也可寫的。
NIO的代碼示例有兩個
沒有引入多路復用器的NIO
服務端
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * @Title:Nio服務端 * @Author:wangchenggong * @Date 2021/4/14 11:04 * @Description * @Version */ public class NioServer { /** * 保存客戶端連接 */ static List<SocketChannel> channelList = new ArrayList<>(); public static void main(String[] args) throws IOException { //創建Nio ServerSocketChannel ServerSocketChannel serverSocket = ServerSocketChannel.open(); serverSocket.socket().bind(new InetSocketAddress(9000)); //設置ServerSocketChannel為非阻塞 serverSocket.configureBlocking(false); System.out.println("Nio服務啟動成功"); while(true){ //非阻塞模式accept方法不會阻塞 /// NIO的非阻塞是由操作系統內部實現的,底層調用瞭linux內核的accept函數 SocketChannel socketChannel = serverSocket.accept(); if(socketChannel != null){ System.out.println("連接成功"); socketChannel.configureBlocking(false); channelList.add(socketChannel); } Iterator<SocketChannel> iterator = channelList.iterator(); while(iterator.hasNext()){ SocketChannel sc = iterator.next(); ByteBuffer byteBuffer = ByteBuffer.allocate(128); //非阻塞模式read方法不會阻塞 int len = sc.read(byteBuffer); if(len > 0){ System.out.println("接收到消息:" + new String(byteBuffer.array())); }else if(len == -1){ iterator.remove(); System.out.println("客戶端斷開連接"); } } } } }
客戶端
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; /** * @Title:Nio客戶端 * @Author:wangchenggong * @Date 2021/4/14 11:36 * @Description * @Version */ public class NioClient { public static void main(String[] args) throws IOException { SocketChannel socketChannel=SocketChannel.open(new InetSocketAddress("localhost", 9000)); socketChannel.configureBlocking(false); ByteBuffer writeBuffer=ByteBuffer.wrap("HelloServer1".getBytes()); socketChannel.write(writeBuffer); System.out.println("向服務端發送數據1結束"); writeBuffer = ByteBuffer.wrap("HelloServer2".getBytes()); socketChannel.write(writeBuffer); System.out.println("向服務端發送數據2結束"); writeBuffer = ByteBuffer.wrap("HelloServer3".getBytes()); socketChannel.write(writeBuffer); System.out.println("向服務端發送數據3結束"); } }
引入瞭多路復用器的NIO
服務端
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.Iterator; import java.util.Set; /** * @Title:引入多路復用器後的NIO服務端 * @Author:wangchenggong * @Date 2021/4/14 13:57 * @Description * SelectionKey.OP_ACCEPT —— 接收連接繼續事件,表示服務器監聽到瞭客戶連接,服務器可以接收這個連接瞭 * SelectionKey.OP_CONNECT —— 連接就緒事件,表示客戶與服務器的連接已經建立成功 * SelectionKey.OP_READ —— 讀就緒事件,表示通道中已經有瞭可讀的數據,可以執行讀操作瞭(通道目前有數據,可以進行讀操作瞭) * SelectionKey.OP_WRITE —— 寫就緒事件,表示已經可以向通道寫數據瞭(通道目前可以用於寫操作) * * 1.當向通道中註冊SelectionKey.OP_READ事件後,如果客戶端有向緩存中write數據,下次輪詢時,則會 isReadable()=true; * * 2.當向通道中註冊SelectionKey.OP_WRITE事件後,這時你會發現當前輪詢線程中isWritable()一直為true,如果不設置為其他事件 * @Version */ public class NioSelectorServer { public static void main(String[] args) throws IOException { /** * 創建server端,並且向多路復用器註冊,讓多路復用器監聽連接事件 */ //創建ServerSocketChannel ServerSocketChannel serverSocket = ServerSocketChannel.open(); serverSocket.socket().bind(new InetSocketAddress(9000)); //設置ServerSocketChannel為非阻塞 serverSocket.configureBlocking(false); //打開selector處理channel,即創建epoll Selector selector = Selector.open(); //把ServerSocketChannel註冊到selector上,並且selector對客戶端的accept連接操作感興趣 serverSocket.register(selector, SelectionKey.OP_ACCEPT); System.out.println("NioSelectorServer服務啟動成功"); while(true){ //阻塞等待需要處理的事件發生 selector.select(); //獲取selector中註冊的全部事件的SelectionKey實例 Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); //遍歷selectionKeys,對事件進行處理 while (iterator.hasNext()){ SelectionKey key = iterator.next(); //如果是OP_ACCEPT事件,則進行連接和事件註冊 if(key.isAcceptable()){ ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); //接受客戶端的連接 SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); //把SocketChannel註冊到selector上,並且selector對客戶端的read操作(即讀取來自客戶端的消息)感興趣 socketChannel.register(selector, SelectionKey.OP_READ); System.out.println("客戶端"+socketChannel.getRemoteAddress()+"連接成功!"); }else if(key.isReadable()){ SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(128); int len = socketChannel.read(byteBuffer); if(len > 0){ System.out.println("接收到客戶端"+socketChannel.getRemoteAddress()+"發來的消息,消息內容為:"+new String(byteBuffer.array())); }else if(len == -1){ System.out.println("客戶端斷開連接"); //關閉該客戶端 socketChannel.close(); } } //從事件集合裡刪除本次處理的key,防止下次select重復處理 iterator.remove(); } } /** * NioSelectorServer服務啟動成功 * 客戶端/127.0.0.1:57070連接成功! * 接收到客戶端/127.0.0.1:57070發來的消息,消息內容為:HelloServer * 客戶端/127.0.0.1:57121連接成功! * 接收到客戶端/127.0.0.1:57121發來的消息,消息內容為:HelloServer */ } }
客戶端
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.SocketChannel; import java.util.Iterator; import java.util.Set; /** * @Title:引入多路復用器後的NIO客戶端 * @Author:wangchenggong * @Date 2021/4/14 14:39 * @Description * @Version */ public class NioSelectorClient { public static void main(String[] args) throws IOException { SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); Selector selector = Selector.open(); //要先向多路復用器註冊,然後才可以跟服務端進行連接 socketChannel.register(selector, SelectionKey.OP_CONNECT); socketChannel.connect(new InetSocketAddress("localhost", 9000)); while (true){ selector.select(); Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> iterator = keys.iterator(); while (iterator.hasNext()){ SelectionKey key = iterator.next(); iterator.remove(); if (key.isConnectable()){ SocketChannel sc = (SocketChannel) key.channel(); if (sc.finishConnect()){ System.out.println("服務器連接成功"); ByteBuffer writeBuffer=ByteBuffer.wrap("HelloServer".getBytes()); sc.write(writeBuffer); System.out.println("向服務端發送數據結束"); } } } } /** * 服務器連接成功 * 向服務端發送數據結束 */ } }
三、AIO(Asynchronous IO) 即NIO2.0
異步非阻塞,由操作系統完成後回調通知服務端程序啟動線程去處理,一般適用於連接數較多且連接時間較長的應用。
應用場景:AIO方式適用於連接數目多且連接時間較長(重操作)的架構(應用),JDK7開始支持。
著名的異步網絡通訊框架netty之所以廢棄瞭AIO,原因是:在Linux系統上,NIO的底層實現使用瞭Epoll,而AIO的底層實現仍使用Epoll,沒有很好實現AIO,因此在性能上沒有明顯的優勢,而且被JDK封裝瞭一層不容易深度優 化,Linux上AIO還不夠成熟
AIO示例代碼如下:
服務端
import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousServerSocketChannel; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; /** * @Title:Aio服務端 * @Author:wangchenggong * @Date 2021/4/14 17:05 * @Description * @Version */ public class AioServer { public static void main(String[] args) throws Exception { final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(9000)); serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() { @Override public void completed(AsynchronousSocketChannel socketChannel, Object attachment) { try{ System.out.println("2--"+Thread.currentThread().getName()); //接收客戶端連接 serverChannel.accept(attachment,this); System.out.println("客戶端"+socketChannel.getRemoteAddress()+"已連接"); ByteBuffer buffer = ByteBuffer.allocate(128); socketChannel.read(buffer, null, new CompletionHandler<Integer, Object>() { @Override public void completed(Integer result, Object attachment) { System.out.println("3--"+Thread.currentThread().getName()); //flip方法將Buffer從寫模式切換到讀模式 //如果沒有,就是從文件最後開始讀取的,當然讀出來的都是byte=0時候的字符。通過buffer.flip();這個語句,就能把buffer的當前位置更改為buffer緩沖區的第一個位置 buffer.flip(); System.out.println(new String(buffer.array(), 0, result)); socketChannel.write(ByteBuffer.wrap("hello Aio Client!".getBytes())); } @Override public void failed(Throwable exc, Object attachment) { exc.printStackTrace(); } }); }catch(Exception e){ e.printStackTrace(); } } @Override public void failed(Throwable exc, Object attachment) { } }); System.out.println("1‐‐main"+Thread.currentThread().getName()); Thread.sleep(Integer.MAX_VALUE); } /** * 1‐‐mainmain * 2--Thread-9 * 客戶端/127.0.0.1:54821已連接 * 3--Thread-8 * hello AIO server ! * 2--Thread-9 * 客戶端/127.0.0.1:54942已連接 * 3--Thread-7 * hello AIO server ! */ }
客戶端
import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; /** * @Title:Aio客戶端 * @Author:wangchenggong * @Date 2021/4/14 16:56 * @Description * @Version */ public class AioClient { public static void main(String[] args) throws Exception { //創建Aio客戶端 AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open(); socketChannel.connect(new InetSocketAddress("localhost", 9000)).get(); //發送消息 socketChannel.write(ByteBuffer.wrap("hello AIO server !".getBytes())); //接收消息 ByteBuffer buffer = ByteBuffer.allocate(128); Integer len = socketChannel.read(buffer).get(); if(len != -1){ //客戶端收到消息:hello Aio Client! System.out.println("客戶端收到消息:"+new String(buffer.array(), 0, len)); } } }
四、總結
BIO | NIO | AIO | |
IO模型 | 同步阻塞 | 同步非阻塞 | 異步非阻塞 |
編程難度 | 簡單 | 復雜 | 復雜 |
可靠性 好 | 差 | 好 | 好 |
吞吐量 | 低 | 高 | 高 |
到此這篇關於java中BIO、NIO、AIO都有啥區別的文章就介紹到這瞭,更多相關java中BIO、NIO、AIO的區別內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- None Found