您好,登錄后才能下訂單哦!
這篇文章主要介紹了Java中NIO的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
I/O模型的本質是用什么樣的通道進行數據的發送和接收,很大程度上決定了程序通信的性能。
Java共支持三種網絡編程模型:BIO、NIO、AIO
BIO:同步并阻塞,服務實現模式為一個連接一個線程,即客戶端有一個連接請求時,服務端就需要啟動一個線程進行處理。
NIO: 同步非阻塞,服務器實現模式為一個線程處理多個請求連接,即客戶端發送的請求都會注冊到多路復用器上,多路復用器輪詢到連接有I/O請求就進行處理。
AIO:異步非阻塞,AIO引入異步通道的概念,采用了Proactor模式,簡化了程序編寫,有效的請求才啟動線程,它的特點是先由操作系統完成后才通知服務端。
BIO方式適用于連接數目比較小且固定的架構,這種方式對服務器資源要求比較高, 并發局限于應用中,JDK1.4以前的唯一選擇,但程序簡單易理解。
NIO方式適用于連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,彈幕 系統,服務器間通訊等。編程比較復雜,JDK1.4開始支持。
AIO方式使用于連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分 調用OS參與并發操作,編程比較復雜,JDK7開始支持
服務器端啟動一個ServerSocket;
客戶端啟動Socket對服務器進行通 信,默認情況下服務器端需要對每 個客戶 建立一個線程與之通訊;
客戶端發出請求后, 先咨詢服務器 是否有線程響應,如果沒有則會等 待,或者被拒絕;
如果有響應,客戶端線程會等待請 求結束后,在繼續執行;
NIO 有三大核心部分:Selector(選擇器)、Channel(通道)、Buffer(緩沖區)。
NIO是面向緩沖區,或者說面向塊編程,數據讀取到一個 它稍后處理的緩沖區,需要時可在緩沖區中前后移動,這就 增加了處理過程中的靈活性,使用它可以提供非阻塞式的高伸縮性網絡。
HTTP2.0使用了多路復用的技術,做到同一個連接并發處理多個請求,而且并發請求 的數量比HTTP1.1大了好幾個數量級。
簡而言之,NIO可以一個線程處理多個請求。
BIO 以流的方式處理數據,而 NIO 以塊的方式處理數據,塊 I/O 的效率比流 I/O 高很多;
BIO 是阻塞的,NIO 則是非阻塞的;
BIO基于字節流和字符流進行操作,而 NIO 基于 Channel(通道)和 Buffer(緩沖區)進 行操作,數據總是從通道讀取到緩沖區中,或者從緩沖區寫入到通道中。Selector(選擇器)用于監聽多個通道的事件(比如:連接請求,數據到達等),因 此使用單個線程就可以監聽多個客戶端通道。
流程圖說明:
Selector 對應一個線程, 一個線程對應多個channel(連接);
該圖反應了有三個channel 注冊到 該selector //程序;
每個channel 都會對應一個Buffer;
程序切換到哪個channel 是有事件決定的, Event 就是一個重要的概念;
Selector 會根據不同的事件,在各個通道上切換;
Buffer 就是一個內存塊 , 底層是有一個數組;
數據的讀取寫入是通過Buffer, 這個和BIO , BIO 中要么是輸入流,或者是 輸出流, 不能雙向,但是NIO的Buffer 是可以讀也可以寫, 需要 flip 方法切換;
channel 是雙向的, 可以返回底層操作系統的情況, 比如Linux , 底層的操作系統 通道就是雙向的;
緩沖區本質上是一個可以讀寫數據的內存塊,可以理解成是一個 容器對象(含數組),該對象提供了一組方法,可以更輕松地使用內存塊,,緩沖區對 象內置了一些機制,能夠跟蹤和記錄緩沖區的狀態變化情況。Channel 提供從文件、 網絡讀取數據的渠道,但是讀取或寫入的數據都必須經由 Buffer。
在 NIO 中,Buffer 是一個頂層父類,它是一個抽象類。
ByteBuffer,存儲字節數據到緩沖區;
ShortBuffer,存儲字符串數據到緩沖區;
CharBuffer,存儲字符數據到緩沖區;
IntBuffer,存儲整數數據到緩沖區;
LongBuffer,存儲長整型數據到緩沖區;
DoubleBuffer,存儲小數到緩沖區;
FloatBuffer,存儲小數到緩沖區;
mark:標記
position:位置,下一個要被讀或寫的元素的索引, 每次讀寫緩沖區數據時都會改變改值, 為下次讀寫作準備。
limit:表示緩沖區的當前終點,不能對緩沖區 超過極限的位置進行讀寫操作。且極限 是可以修改的
capacity:容量,即可以容納的最大數據量;在緩 沖區創建時被設定并且不能改變。
JDK1.4時,引入的api
public final int capacity( )//返回此緩沖區的容量
public final int position( )//返回此緩沖區的位置
public final Buffer position (int newPositio)//設置此緩沖區的位置
public final int limit( )//返回此緩沖區的限制
public final Buffer limit (int newLimit)//設置此緩沖區的限制
public final Buffer mark( )//在此緩沖區的位置設置標記
public final Buffer reset( )//將此緩沖區的位置重置為以前標記的位置
public final Buffer clear( )//清除此緩沖區, 即將各個標記恢復到初始狀態,但是數據并沒有真正擦除, 后面操作會覆蓋
public final Buffer flip( )//反轉此緩沖區
public final Buffer rewind( )//重繞此緩沖區
public final int remaining( )//返回當前位置與限制之間的元素數
public final boolean hasRemaining( )//告知在當前位置和限制之間是否有元素
public abstract boolean isReadOnly( );//告知此緩沖區是否為只讀緩沖區
JDK1.6時引入的api
public abstract boolean hasArray();//告知此緩沖區是否具有可訪問的底層實現數組
public abstract Object array();//返回此緩沖區的底層實現數組
public abstract int arrayOffset();//返回此緩沖區的底層實現數組中第一個緩沖區元素的偏移量
public abstract boolean isDirect();//告知此緩沖區是否為直接緩沖區
(1)NIO的通道類似于流
通道可以同時進行讀寫,而流只能讀或者只能寫;
通道可以實現異步讀寫數據
通道可以從緩沖讀數據,也可以寫數據到緩沖
(2)BIO 中的 stream 是單向的,例如 FileInputStream 對 象只能進行讀取數據的操作,而 NIO 中的通道 (Channel)是雙向的,可以讀操作,也可以寫操作。
(3)Channel在NIO中是一個接口
(4)常用的 Channel 類有:FileChannel、 DatagramChannel、ServerSocketChannel 和 SocketChannel。ServerSocketChanne 類似 ServerSocket , SocketChannel 類似 Socket。
(5)FileChannel 用于文件的數據讀寫, DatagramChannel 用于 UDP 的數據讀寫, ServerSocketChannel 和 SocketChannel 用于 TCP 的數據讀寫。
FileChannel主要用來對本地文件進行 IO 操作,常見的方法有:
read,從通道讀取數據并放到緩沖區中
write,把緩沖區的數據寫到通道中
transferFrom,從目標通道 中復制數據到當前通道
transferTo,把數據從當 前通道復制給目標通道
ByteBuffer 支持類型化的put 和 get, put 放入的是什么數據類型,get就應該使用 相應的數據類型來取出,否則可能有 BufferUnderflowException 異常。
可以將一個普通Buffer 轉成只讀Buffer。
NIO 還提供了 MappedByteBuffer, 可以讓文件直接在內存(堆外的內存)中進 行修改, 而如何同步到文件由NIO 來完成。
NIO 還支持 通過多個 Buffer (即 Buffer 數組) 完成讀寫操作,即 Scattering 和 Gathering。
Java 的 NIO,用非阻塞的 IO 方式。可以用一個線程,處理多個的客戶端連 接,就會使用到Selector(選擇器)。
Selector 能夠檢測多個注冊的通道上是否有事件發生,如果有事件發生,便獲取事件然 后針對每個事件進行相應的處理。這樣就可以只用一個單線程去管理多個 通道,也就是管理多個連接和請求。
只有在 連接/通道 真正有讀寫事件發生時,才會進行讀寫,就大大地減少 了系統開銷,并且不必為每個連接都創建一個線程,不用去維護多個線程。
避免了多線程之間的上下文切換導致的開銷。
open();//得到一個選擇器對象
select(long timeout);//監控所有注冊的通道,當其 中有 IO 操作可以進行時,將 對應的 SelectionKey 加入到內部集合中并返回,參數用來 設置超時時間
selectedKeys();//從內部集合中得 到所有的 SelectionKey。
NIO中的 ServerSocketChannel功能類似ServerSocket,SocketChannel功能類 似Socket。
package com.nezha.guor.nio;import java.io.IOException;import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.*;import java.util.Iterator;public class NioServer { private Selector selector; private ServerSocketChannel serverSocketChannel; private static final int PORT = 8080; public NioServer() { try { //獲得選擇器 selector = Selector.open(); serverSocketChannel = ServerSocketChannel.open(); //綁定端口 serverSocketChannel.socket().bind(new InetSocketAddress(PORT)); //設置非阻塞模式 serverSocketChannel.configureBlocking(false); //將該ServerSocketChannel 注冊到selector serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); }catch (IOException e) { System.out.println("NioServer error:"+e.getMessage()); } } public void listen() { System.out.println("監聽線程啟動: " + Thread.currentThread().getName()); try { while (true) { int count = selector.select(); if(count > 0) { //遍歷得到selectionKey集合 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); if(key.isAcceptable()) { SocketChannel sc = serverSocketChannel.accept(); sc.configureBlocking(false); sc.register(selector, SelectionKey.OP_READ); System.out.println(sc.getRemoteAddress() + " 上線 "); } //通道發送read事件,即通道是可讀的狀態 if(key.isReadable()) { getDataFromChannel(key); } //當前的key 刪除,防止重復處理 iterator.remove(); } } else { System.out.println("等待中"); } } }catch (Exception e) { System.out.println("listen error:"+e.getMessage()); } } private void getDataFromChannel(SelectionKey key) { SocketChannel channel = null; try { channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int count = channel.read(buffer); //根據count的值做處理 if(count > 0) { String msg = new String(buffer.array()); System.out.println("來自客戶端: " + msg); //向其它的客戶端轉發消息(排除自己) sendInfoToOtherClients(msg, channel); } }catch (IOException e) { try { System.out.println(channel.getRemoteAddress() + " 離線了"); //取消注冊 key.cancel(); }catch (IOException ex) { System.out.println("getDataFromChannel error:"+ex.getMessage()); } }finally { try { channel.close(); }catch (IOException ex) { System.out.println("channel.close() error:"+ex.getMessage()); } } } //轉發消息給其它客戶(通道) private void sendInfoToOtherClients(String msg, SocketChannel self ) throws IOException{ System.out.println("服務器轉發消息中..."); System.out.println("服務器轉發數據給客戶端線程: " + Thread.currentThread().getName()); //遍歷 所有注冊到selector 上的 SocketChannel,并排除 self for(SelectionKey key: selector.keys()) { Channel targetChannel = key.channel(); //排除自己 if(targetChannel instanceof SocketChannel && targetChannel != self) { SocketChannel dest = (SocketChannel)targetChannel; //將信息存儲到buffer ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes()); //將buffer數據寫入通道 dest.write(buffer); } } } public static void main(String[] args) { //創建服務器對象 NioServer nioServer = new NioServer(); nioServer.listen(); }}
package com.nezha.guor.nio;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.Scanner;public class NioClient { private final int PORT = 8080; //服務器端口 private Selector selector; private SocketChannel socketChannel; private String username; public NioClient() throws IOException { selector = Selector.open(); socketChannel = socketChannel.open(new InetSocketAddress("127.0.0.1", PORT)); //設置非阻塞 socketChannel.configureBlocking(false); //將channel注冊到selector socketChannel.register(selector, SelectionKey.OP_READ); username = socketChannel.getLocalAddress().toString().substring(1); System.out.println(username + " is ok..."); } //向服務器發送消息 public void sendInfo(String info) { info = username + " 說:" + info; try { socketChannel.write(ByteBuffer.wrap(info.getBytes())); }catch (IOException e) { System.out.println("sendInfo error:"+e.getMessage()); } } //讀取從服務器端回復的消息 public void readInfo() { try { int readChannels = selector.select(); if(readChannels > 0) { Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); if(key.isReadable()) { //得到相關的通道 SocketChannel sc = (SocketChannel) key.channel(); //得到一個Buffer ByteBuffer buffer = ByteBuffer.allocate(1024); //讀取 sc.read(buffer); //把讀到的緩沖區的數據轉成字符串 String msg = new String(buffer.array()); System.out.println(msg.trim()); } } iterator.remove(); //刪除當前的selectionKey, 防止重復操作 } else { System.out.println("沒有可以用的通道..."); } }catch (Exception e) { System.out.println("readInfo error:"+e.getMessage()); } } public static void main(String[] args) throws Exception { NioClient nioClient = new NioClient(); new Thread() { public void run() { while (true) { nioClient.readInfo(); try { Thread.currentThread().sleep(2000); }catch (InterruptedException e) { System.out.println("sleep error:"+e.getMessage()); } } } }.start(); //發送數據給服務器端 Scanner scanner = new Scanner(System.in); while (scanner.hasNextLine()) { nioClient.sendInfo(scanner.nextLine()); } }}
感謝你能夠認真閱讀完這篇文章,希望小編分享的“Java中NIO的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。