本篇文章給大家?guī)?lái)了關(guān)于java的相關(guān)知識(shí),其中主要介紹了NIO的相關(guān)問(wèn)題,包括了NIO核心、BIO與NIO比較、通過(guò)NIO實(shí)現(xiàn)簡(jiǎn)單的服務(wù)端客戶端通信,希望對(duì)大家有幫助。
推薦學(xué)習(xí):《java教程》
一、Java思維導(dǎo)圖
二、I/O模型
I/O模型的本質(zhì)是用什么樣的通道進(jìn)行數(shù)據(jù)的發(fā)送和接收,很大程度上決定了程序通信的性能。
Java共支持三種網(wǎng)絡(luò)編程模型:BIO、NIO、AIO
-
BIO:同步并阻塞,服務(wù)實(shí)現(xiàn)模式為一個(gè)連接一個(gè)線程,即客戶端有一個(gè)連接請(qǐng)求時(shí),服務(wù)端就需要啟動(dòng)一個(gè)線程進(jìn)行處理。
-
NIO: 同步非阻塞,服務(wù)器實(shí)現(xiàn)模式為一個(gè)線程處理多個(gè)請(qǐng)求連接,即客戶端發(fā)送的請(qǐng)求都會(huì)注冊(cè)到多路復(fù)用器上,多路復(fù)用器輪詢(xún)到連接有I/O請(qǐng)求就進(jìn)行處理。
-
AIO:異步非阻塞,AIO引入異步通道的概念,采用了Proactor模式,簡(jiǎn)化了程序編寫(xiě),有效的請(qǐng)求才啟動(dòng)線程,它的特點(diǎn)是先由操作系統(tǒng)完成后才通知服務(wù)端。
三、BIO、NIO、AIO應(yīng)用場(chǎng)景
-
BIO方式適用于連接數(shù)目比較小且固定的架構(gòu),這種方式對(duì)服務(wù)器資源要求比較高, 并發(fā)局限于應(yīng)用中,JDK1.4以前的唯一選擇,但程序簡(jiǎn)單易理解。
-
NIO方式適用于連接數(shù)目多且連接比較短(輕操作)的架構(gòu),比如聊天服務(wù)器,彈幕 系統(tǒng),服務(wù)器間通訊等。編程比較復(fù)雜,JDK1.4開(kāi)始支持。
-
AIO方式使用于連接數(shù)目多且連接比較長(zhǎng)(重操作)的架構(gòu),比如相冊(cè)服務(wù)器,充分 調(diào)用OS參與并發(fā)操作,編程比較復(fù)雜,JDK7開(kāi)始支持
四、BIO編程簡(jiǎn)單流程
-
服務(wù)器端啟動(dòng)一個(gè)ServerSocket;
-
客戶端啟動(dòng)Socket對(duì)服務(wù)器進(jìn)行通 信,默認(rèn)情況下服務(wù)器端需要對(duì)每 個(gè)客戶 建立一個(gè)線程與之通訊;
-
客戶端發(fā)出請(qǐng)求后, 先咨詢(xún)服務(wù)器 是否有線程響應(yīng),如果沒(méi)有則會(huì)等 待,或者被拒絕;
-
如果有響應(yīng),客戶端線程會(huì)等待請(qǐng) 求結(jié)束后,在繼續(xù)執(zhí)行;
五、NIO核心
NIO 有三大核心部分:Selector(選擇器)、Channel(通道)、Buffer(緩沖區(qū))。
NIO是面向緩沖區(qū),或者說(shuō)面向塊編程,數(shù)據(jù)讀取到一個(gè) 它稍后處理的緩沖區(qū),需要時(shí)可在緩沖區(qū)中前后移動(dòng),這就 增加了處理過(guò)程中的靈活性,使用它可以提供非阻塞式的高伸縮性網(wǎng)絡(luò)。
HTTP2.0使用了多路復(fù)用的技術(shù),做到同一個(gè)連接并發(fā)處理多個(gè)請(qǐng)求,而且并發(fā)請(qǐng)求 的數(shù)量比HTTP1.1大了好幾個(gè)數(shù)量級(jí)。
簡(jiǎn)而言之,NIO可以一個(gè)線程處理多個(gè)請(qǐng)求。
六、BIO與NIO比較
-
BIO 以流的方式處理數(shù)據(jù),而 NIO 以塊的方式處理數(shù)據(jù),塊 I/O 的效率比流 I/O 高很多;
-
BIO 是阻塞的,NIO 則是非阻塞的;
-
BIO基于字節(jié)流和字符流進(jìn)行操作,而 NIO 基于 Channel(通道)和 Buffer(緩沖區(qū))進(jìn) 行操作,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中,或者從緩沖區(qū)寫(xiě)入到通道中。Selector(選擇器)用于監(jiān)聽(tīng)多個(gè)通道的事件(比如:連接請(qǐng)求,數(shù)據(jù)到達(dá)等),因 此使用單個(gè)線程就可以監(jiān)聽(tīng)多個(gè)客戶端通道。
七、NIO 三大核心原理示意圖
流程圖說(shuō)明:
-
Selector 對(duì)應(yīng)一個(gè)線程, 一個(gè)線程對(duì)應(yīng)多個(gè)channel(連接);
-
該圖反應(yīng)了有三個(gè)channel 注冊(cè)到 該selector //程序;
-
每個(gè)channel 都會(huì)對(duì)應(yīng)一個(gè)Buffer;
-
程序切換到哪個(gè)channel 是有事件決定的, Event 就是一個(gè)重要的概念;
-
Selector 會(huì)根據(jù)不同的事件,在各個(gè)通道上切換;
-
Buffer 就是一個(gè)內(nèi)存塊 , 底層是有一個(gè)數(shù)組;
-
數(shù)據(jù)的讀取寫(xiě)入是通過(guò)Buffer, 這個(gè)和BIO , BIO 中要么是輸入流,或者是 輸出流, 不能雙向,但是NIO的Buffer 是可以讀也可以寫(xiě), 需要 flip 方法切換;
-
channel 是雙向的, 可以返回底層操作系統(tǒng)的情況, 比如Linux , 底層的操作系統(tǒng) 通道就是雙向的;
八、緩沖區(qū)(buffer)
緩沖區(qū)本質(zhì)上是一個(gè)可以讀寫(xiě)數(shù)據(jù)的內(nèi)存塊,可以理解成是一個(gè) 容器對(duì)象(含數(shù)組),該對(duì)象提供了一組方法,可以更輕松地使用內(nèi)存塊,,緩沖區(qū)對(duì) 象內(nèi)置了一些機(jī)制,能夠跟蹤和記錄緩沖區(qū)的狀態(tài)變化情況。Channel 提供從文件、 網(wǎng)絡(luò)讀取數(shù)據(jù)的渠道,但是讀取或?qū)懭氲臄?shù)據(jù)都必須經(jīng)由 Buffer。
在 NIO 中,Buffer 是一個(gè)頂層父類(lèi),它是一個(gè)抽象類(lèi)。
1、常用Buffer子類(lèi)一覽
-
ByteBuffer,存儲(chǔ)字節(jié)數(shù)據(jù)到緩沖區(qū);
-
ShortBuffer,存儲(chǔ)字符串?dāng)?shù)據(jù)到緩沖區(qū);
-
CharBuffer,存儲(chǔ)字符數(shù)據(jù)到緩沖區(qū);
-
IntBuffer,存儲(chǔ)整數(shù)數(shù)據(jù)到緩沖區(qū);
-
LongBuffer,存儲(chǔ)長(zhǎng)整型數(shù)據(jù)到緩沖區(qū);
-
DoubleBuffer,存儲(chǔ)小數(shù)到緩沖區(qū);
-
FloatBuffer,存儲(chǔ)小數(shù)到緩沖區(qū);
2、buffer四大屬性
-
mark:標(biāo)記
-
position:位置,下一個(gè)要被讀或?qū)懙脑氐乃饕?每次讀寫(xiě)緩沖區(qū)數(shù)據(jù)時(shí)都會(huì)改變改值, 為下次讀寫(xiě)作準(zhǔn)備。
-
limit:表示緩沖區(qū)的當(dāng)前終點(diǎn),不能對(duì)緩沖區(qū) 超過(guò)極限的位置進(jìn)行讀寫(xiě)操作。且極限 是可以修改的
-
capacity:容量,即可以容納的最大數(shù)據(jù)量;在緩 沖區(qū)創(chuàng)建時(shí)被設(shè)定并且不能改變。
3、buffer常用api
JDK1.4時(shí),引入的api
- public final int capacity( )//返回此緩沖區(qū)的容量
- public final int position( )//返回此緩沖區(qū)的位置
- public final Buffer position (int newPositio)//設(shè)置此緩沖區(qū)的位置
- public final int limit( )//返回此緩沖區(qū)的限制
- public final Buffer limit (int newLimit)//設(shè)置此緩沖區(qū)的限制
- public final Buffer mark( )//在此緩沖區(qū)的位置設(shè)置標(biāo)記
- public final Buffer reset( )//將此緩沖區(qū)的位置重置為以前標(biāo)記的位置
- public final Buffer clear( )//清除此緩沖區(qū), 即將各個(gè)標(biāo)記恢復(fù)到初始狀態(tài),但是數(shù)據(jù)并沒(méi)有真正擦除, 后面操作會(huì)覆蓋
- public final Buffer flip( )//反轉(zhuǎn)此緩沖區(qū)
- public final Buffer rewind( )//重繞此緩沖區(qū)
- public final int remaining( )//返回當(dāng)前位置與限制之間的元素?cái)?shù)
- public final boolean hasRemaining( )//告知在當(dāng)前位置和限制之間是否有元素
- public abstract boolean isReadOnly( );//告知此緩沖區(qū)是否為只讀緩沖區(qū)
JDK1.6時(shí)引入的api
- public abstract boolean hasArray();//告知此緩沖區(qū)是否具有可訪問(wèn)的底層實(shí)現(xiàn)數(shù)組
- public abstract Object array();//返回此緩沖區(qū)的底層實(shí)現(xiàn)數(shù)組
- public abstract int arrayOffset();//返回此緩沖區(qū)的底層實(shí)現(xiàn)數(shù)組中第一個(gè)緩沖區(qū)元素的偏移量
- public abstract boolean isDirect();//告知此緩沖區(qū)是否為直接緩沖區(qū)
九、通道(channel)
1、基本介紹
(1)NIO的通道類(lèi)似于流
- 通道可以同時(shí)進(jìn)行讀寫(xiě),而流只能讀或者只能寫(xiě);
- 通道可以實(shí)現(xiàn)異步讀寫(xiě)數(shù)據(jù)
- 通道可以從緩沖讀數(shù)據(jù),也可以寫(xiě)數(shù)據(jù)到緩沖
(2)BIO 中的 stream 是單向的,例如 FileInputStream 對(duì) 象只能進(jìn)行讀取數(shù)據(jù)的操作,而 NIO 中的通道 (Channel)是雙向的,可以讀操作,也可以寫(xiě)操作。
(3)Channel在NIO中是一個(gè)接口
(4)常用的 Channel 類(lèi)有:FileChannel、 DatagramChannel、ServerSocketChannel 和 SocketChannel。ServerSocketChanne 類(lèi)似 ServerSocket , SocketChannel 類(lèi)似 Socket。
(5)FileChannel 用于文件的數(shù)據(jù)讀寫(xiě), DatagramChannel 用于 UDP 的數(shù)據(jù)讀寫(xiě), ServerSocketChannel 和 SocketChannel 用于 TCP 的數(shù)據(jù)讀寫(xiě)。
2、FileChannel
FileChannel主要用來(lái)對(duì)本地文件進(jìn)行 IO 操作,常見(jiàn)的方法有:
-
read,從通道讀取數(shù)據(jù)并放到緩沖區(qū)中
-
write,把緩沖區(qū)的數(shù)據(jù)寫(xiě)到通道中
-
transferFrom,從目標(biāo)通道 中復(fù)制數(shù)據(jù)到當(dāng)前通道
-
transferTo,把數(shù)據(jù)從當(dāng) 前通道復(fù)制給目標(biāo)通道
3、關(guān)于Buffer 和 Channel的注意事項(xiàng)和細(xì)節(jié)
-
ByteBuffer 支持類(lèi)型化的put 和 get, put 放入的是什么數(shù)據(jù)類(lèi)型,get就應(yīng)該使用 相應(yīng)的數(shù)據(jù)類(lèi)型來(lái)取出,否則可能有 BufferUnderflowException 異常。
-
可以將一個(gè)普通Buffer 轉(zhuǎn)成只讀Buffer。
-
NIO 還提供了 MappedByteBuffer, 可以讓文件直接在內(nèi)存(堆外的內(nèi)存)中進(jìn) 行修改, 而如何同步到文件由NIO 來(lái)完成。
-
NIO 還支持 通過(guò)多個(gè) Buffer (即 Buffer 數(shù)組) 完成讀寫(xiě)操作,即 Scattering 和 Gathering。
十、Selector(選擇器)
1、基本介紹
-
Java 的 NIO,用非阻塞的 IO 方式。可以用一個(gè)線程,處理多個(gè)的客戶端連 接,就會(huì)使用到Selector(選擇器)。
-
Selector 能夠檢測(cè)多個(gè)注冊(cè)的通道上是否有事件發(fā)生,如果有事件發(fā)生,便獲取事件然 后針對(duì)每個(gè)事件進(jìn)行相應(yīng)的處理。這樣就可以只用一個(gè)單線程去管理多個(gè) 通道,也就是管理多個(gè)連接和請(qǐng)求。
-
只有在 連接/通道 真正有讀寫(xiě)事件發(fā)生時(shí),才會(huì)進(jìn)行讀寫(xiě),就大大地減少 了系統(tǒng)開(kāi)銷(xiāo),并且不必為每個(gè)連接都創(chuàng)建一個(gè)線程,不用去維護(hù)多個(gè)線程。
-
避免了多線程之間的上下文切換導(dǎo)致的開(kāi)銷(xiāo)。
2、selector的相關(guān)方法
-
open();//得到一個(gè)選擇器對(duì)象
-
select(long timeout);//監(jiān)控所有注冊(cè)的通道,當(dāng)其 中有 IO 操作可以進(jìn)行時(shí),將 對(duì)應(yīng)的 SelectionKey 加入到內(nèi)部集合中并返回,參數(shù)用來(lái) 設(shè)置超時(shí)時(shí)間
-
selectedKeys();//從內(nèi)部集合中得 到所有的 SelectionKey。
3、注意事項(xiàng)
NIO中的 ServerSocketChannel功能類(lèi)似ServerSocket,SocketChannel功能類(lèi) 似Socket。
十一、通過(guò)NIO實(shí)現(xiàn)簡(jiǎn)單的服務(wù)端客戶端通信
1、服務(wù)端
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)); //設(shè)置非阻塞模式 serverSocketChannel.configureBlocking(false); //將該ServerSocketChannel 注冊(cè)到selector serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); }catch (IOException e) { System.out.println("NioServer error:"+e.getMessage()); } } public void listen() { System.out.println("監(jiān)聽(tīng)線程啟動(dòng): " + 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() + " 上線 "); } //通道發(fā)送read事件,即通道是可讀的狀態(tài) if(key.isReadable()) { getDataFromChannel(key); } //當(dāng)前的key 刪除,防止重復(fù)處理 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); //根據(jù)count的值做處理 if(count > 0) { String msg = new String(buffer.array()); System.out.println("來(lái)自客戶端: " + msg); //向其它的客戶端轉(zhuǎn)發(fā)消息(排除自己) sendInfoToOtherClients(msg, channel); } }catch (IOException e) { try { System.out.println(channel.getRemoteAddress() + " 離線了"); //取消注冊(cè) 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()); } } } //轉(zhuǎn)發(fā)消息給其它客戶(通道) private void sendInfoToOtherClients(String msg, SocketChannel self ) throws IOException{ System.out.println("服務(wù)器轉(zhuǎn)發(fā)消息中..."); System.out.println("服務(wù)器轉(zhuǎn)發(fā)數(shù)據(jù)給客戶端線程: " + Thread.currentThread().getName()); //遍歷 所有注冊(cè)到selector 上的 SocketChannel,并排除 self for(SelectionKey key: selector.keys()) { Channel targetChannel = key.channel(); //排除自己 if(targetChannel instanceof SocketChannel && targetChannel != self) { SocketChannel dest = (SocketChannel)targetChannel; //將信息存儲(chǔ)到buffer ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes()); //將buffer數(shù)據(jù)寫(xiě)入通道 dest.write(buffer); } } } public static void main(String[] args) { //創(chuàng)建服務(wù)器對(duì)象 NioServer nioServer = new NioServer(); nioServer.listen(); }}
2、客戶端
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; //服務(wù)器端口 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)); //設(shè)置非阻塞 socketChannel.configureBlocking(false); //將channel注冊(cè)到selector socketChannel.register(selector, SelectionKey.OP_READ); username = socketChannel.getLocalAddress().toString().substring(1); System.out.println(username + " is ok..."); } //向服務(wù)器發(fā)送消息 public void sendInfo(String info) { info = username + " 說(shuō):" + info; try { socketChannel.write(ByteBuffer.wrap(info.getBytes())); }catch (IOException e) { System.out.println("sendInfo error:"+e.getMessage()); } } //讀取從服務(wù)器端回復(fù)的消息 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()) { //得到相關(guān)的通道 SocketChannel sc = (SocketChannel) key.channel(); //得到一個(gè)Buffer ByteBuffer buffer = ByteBuffer.allocate(1024); //讀取 sc.read(buffer); //把讀到的緩沖區(qū)的數(shù)據(jù)轉(zhuǎn)成字符串 String msg = new String(buffer.array()); System.out.println(msg.trim()); } } iterator.remove(); //刪除當(dāng)前的selectionKey, 防止重復(fù)操作 } else { System.out.println("沒(méi)有可以用的通道..."); } }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(); //發(fā)送數(shù)據(jù)給服務(wù)器端 Scanner scanner = new Scanner(System.in); while (scanner.hasNextLine()) { nioClient.sendInfo(scanner.nextLine()); } }}
3、控制臺(tái)輸出
推薦學(xué)習(xí):《java教程》