柚子快報激活碼778899分享:NIO 非阻塞式IO
NIO
Java NIO 基本介紹
Java NIO 全稱 Java non-blocking IO,是指 JDK 提供的新 API。從 JDK1.4 開始,Java 提供了一系列改進的輸入/輸出的新特性,被統(tǒng)稱為 NIO(即 NewIO),是同步非阻塞的。NIO 相關(guān)類都被放在 java.nio 包及子包下,并且對原 java.io 包中的很多類進行改寫。NIO 有三大核心部分:Channel(通道)、Buffer(緩沖區(qū))、Selector(選擇器) 。NIO 是面向緩沖區(qū),或者面向塊編程的。數(shù)據(jù)讀取到一個它稍后處理的緩沖區(qū),需要時可在緩沖區(qū)中前后移動,這就增加了處理過程中的靈活性,使用它可以提供非阻塞式的高伸縮性網(wǎng)絡(luò)。Java NIO 的非阻塞模式,使一個線程從某通道發(fā)送請求或者讀取數(shù)據(jù),但是它僅能得到目前可用的數(shù)據(jù),如果目前沒有數(shù)據(jù)可用時,就什么都不會獲取,而不是保持線程阻塞,所以直至數(shù)據(jù)變的可以讀取之前,該線程可以繼續(xù)做其他的事情。非阻塞寫也是如此,一個線程請求寫入一些數(shù)據(jù)到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。通俗理解:NIO 是可以做到用一個線程來處理多個操作的。假設(shè)有 10000 個請求過來,根據(jù)實際情況,可以分配 50 或者 100 個線程來處理。不像之前的阻塞 IO 那樣,非得分配 10000 個。HTTP 2.0 使用了多路復(fù)用的技術(shù),做到同一個連接并發(fā)處理多個請求,而且并發(fā)請求的數(shù)量比 HTTP 1.1 大了好幾個數(shù)量級。
NIO 三大核心原理示意圖
一張圖描述 NIO 的 Selector、Channel 和 Buffer 的關(guān)系。
每個 Channel 都會對應(yīng)一個 Buffer。Selector 對應(yīng)一個線程,一個線程對應(yīng)多個 Channel(連接)。該圖反應(yīng)了有三個 Channel 注冊到該 Selector //程序程序切換到哪個 Channel 是由事件決定的,Event 就是一個重要的概念。Selector 會根據(jù)不同的事件,在各個通道上切換。Buffer 就是一個內(nèi)存塊,底層是有一個數(shù)組。數(shù)據(jù)的讀取寫入是通過 Buffer,這個和 BIO是不同的,BIO 中要么是輸入流,或者是輸出流,不能雙向,但是 NIO 的 Buffer 是可以讀也可以寫,需要 flip 方法切換 Channel 是雙向的,可以返回底層操作系統(tǒng)的情況,比如 Linux,底層的操作系統(tǒng)通道就是雙向的。
NIO 和 BIO 的比較
BIO 以流的方式處理數(shù)據(jù),而 NIO 以塊的方式處理數(shù)據(jù),塊 I/O 的效率比流 I/O 高很多。 BIO 是阻塞的,NIO 則是非阻塞的。 BIO 基于字節(jié)流和字符流進行操作,而 NIO 基于 Channel(通道)和 Buffer(緩沖區(qū))進行操作,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中,或者從緩沖區(qū)寫入到通道中。Selector(選擇器)用于監(jiān)聽多個通道的事件(比如:連接請求,數(shù)據(jù)到達等),因此使用單個線程就可以監(jiān)聽多個客戶端通道。 Buffer和Channel之間的數(shù)據(jù)流向是雙向的
緩沖區(qū)(Buffer)
緩沖區(qū)(Buffer):緩沖區(qū)本質(zhì)上是一個可以讀寫數(shù)據(jù)的內(nèi)存塊,可以理解成是一個**容器對象(含數(shù)組)**該對象提供了一組方法,可以更輕松地使用內(nèi)存塊,,緩沖區(qū)對象內(nèi)置了一些機制,能夠跟蹤和記錄緩沖區(qū)的狀態(tài)變化情況。Channel 提供從文件、網(wǎng)絡(luò)讀取數(shù)據(jù)的渠道,但是讀取或?qū)懭氲臄?shù)據(jù)都必須經(jīng)由 Buffer,
通道(Channel)
NIO 的通道類似于流,但有些區(qū)別如下:
通道可以同時進行讀寫,而流只能讀或者只能寫通道可以實現(xiàn)異步讀寫數(shù)據(jù)通道可以從緩沖讀數(shù)據(jù),也可以寫數(shù)據(jù)到緩沖:
BIO 中的 Stream 是單向的,例如 FileInputStream 對象只能進行讀取數(shù)據(jù)的操作,而 NIO 中的通道(Channel)是雙向的,可以讀操作,也可以寫操作。Channel 在 NIO 中是一個接口 public interface Channel extends Closeable{}常用的 Channel 類有:FileChannel、DatagramChannel、ServerSocketChannel 和 SocketChannel?!維erverSocketChanne 類似 ServerSocket、SocketChannel 類似 Socket】FileChannel 用于文件的數(shù)據(jù)讀寫,DatagramChannel 用于 UDP 的數(shù)據(jù)讀寫,ServerSocketChannel 和 SocketChannel 用于 TCP 的數(shù)據(jù)讀寫。
NIO 還支持通過多個 Buffer(即 Buffer數(shù)組)完成讀寫操作,即 Scattering 和 Gathering【舉例說明】
/**
* @Author:jiangdw7
* @date: 2023/8/9 10:04
*/
public class ScatteringAndGatheringTest {
public static void main(String[] args) throws Exception {
//使用 ServerSocketChannel 和 SocketChannel 網(wǎng)絡(luò)
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
//綁定端口到 socket,并啟動
serverSocketChannel.socket().bind(inetSocketAddress);
//創(chuàng)建 buffer 數(shù)組
ByteBuffer[] byteBuffers = new ByteBuffer[2];
byteBuffers[0] = ByteBuffer.allocate(5);
byteBuffers[1] = ByteBuffer.allocate(3);
//等客戶端連接 (telnet)
SocketChannel socketChannel = serverSocketChannel.accept();
int messageLength = 8; //假定從客戶端接收 8 個字節(jié)
//循環(huán)的讀取
while (true) {
int byteRead = 0;
while (byteRead < messageLength) {
long l = socketChannel.read(byteBuffers);
byteRead += l; //累計讀取的字節(jié)數(shù)
System.out.println("byteRead = " + byteRead);
//使用流打印,看看當(dāng)前的這個 buffer 的 position 和 limit
Arrays.asList(byteBuffers).stream().map(buffer -> "position = " + buffer.position() + ", limit = " + buffer.limit()).forEach(System.out::println);
}
//將所有的 buffer 進行 flip
Arrays.asList(byteBuffers).forEach(buffer -> buffer.flip());
//將數(shù)據(jù)讀出顯示到客戶端
long byteWirte = 0;
while (byteWirte < messageLength) {
long l = socketChannel.write(byteBuffers);
byteWirte += l;
}
//將所有的buffer進行clear
Arrays.asList(byteBuffers).forEach(buffer -> {
buffer.clear();
});
System.out.println("byteRead = " + byteRead + ", byteWrite = " + byteWirte + ", messagelength = " + messageLength);
}
}
}
Selector(選擇器)
Java 的 NIO,用非阻塞的 IO 方式??梢杂靡粋€線程,處理多個的客戶端連接,就會使用到 Selector(選擇器)。Selector 能夠檢測多個注冊的通道上是否有事件發(fā)生(注意:多個 Channel 以事件的方式可以注冊到同一個 Selector),如果有事件發(fā)生,便獲取事件然后針對每個事件進行相應(yīng)的處理。這樣就可以只用一個單線程去管理多個通道,也就是管理多個連接和請求。只有在連接/通道真正有讀寫事件發(fā)生時,才會進行讀寫,就大大地減少了系統(tǒng)開銷,并且不必為每個連接都創(chuàng)建一個線程,不用去維護多個線程。避免了多線程之間的上下文切換導(dǎo)致的開銷。
注意事項
NIO 中的 ServerSocketChannel 功能類似 ServerSocket、SocketChannel 功能類似 Socket。Selector 相關(guān)方法說明
selector.select(); //阻塞selector.select(1000); //阻塞 1000 毫秒,在 1000 毫秒后返回selector.wakeup(); //喚醒 selectorselector.selectNow(); //不阻塞,立馬返還
public class NIOClient {
private static Selector selector;
public static void main(String[] args) throws Exception {
selector = Selector.open();
SocketChannel sc = SocketChannel.open();
sc.configureBlocking(false);
sc.connect(new InetSocketAddress("127.0.0.1", 8081));
sc.register(selector, SelectionKey.OP_READ);
ByteBuffer bf = ByteBuffer.allocate(1024);
bf.put("Hi,server,i'm client".getBytes());
if (sc.finishConnect()) {
bf.flip();
while (bf.hasRemaining()) {
sc.write(bf);
}
while (sc.isConnected()) {
selector.select();
Iterator
while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isReadable()) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bf.clear();
SocketChannel othersc = (SocketChannel) key.channel();
while (othersc.read(bf) > 0) {
bf.flip();
while (bf.hasRemaining()) {
bos.write(bf.get());
}
bf.clear();
}
System.out.println("服務(wù)端返回的數(shù)據(jù):" + bos.toString());
Thread.sleep(5000);
sc.close();
System.out.println("客戶端關(guān)閉...");
}
}
selector.selectedKeys().clear();
}
}
}
}
public class NIOServer {
private static Selector selector;
private static ServerSocketChannel serverSocketChannel;
private static ByteBuffer bf = ByteBuffer.allocate(1024);
public static void main(String[] args) throws Exception {
init();
while (true) {
int select = selector.select(10000);
if (select == 0) {
System.out.println("等待連接10秒...");
continue;
}
Iterator
while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isAcceptable()) {
System.out.println("連接準備就緒");
ServerSocketChannel server = (ServerSocketChannel) key.channel();
System.out.println("等待客戶端連接中........................");
SocketChannel channel = server.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
System.out.println("讀準備就緒,開始讀.......................");
SocketChannel channel = (SocketChannel) key.channel();
System.out.println("客戶端的數(shù)據(jù)如下:");
int readLen = 0;
bf.clear();
StringBuffer sb = new StringBuffer();
while ((readLen = channel.read(bf)) > 0) {
bf.flip();
byte[] temp = new byte[readLen];
bf.get(temp, 0, readLen);
sb.append(new String(temp));
bf.clear();
}
if (-1 == readLen) {
System.out.println(channel.hashCode()+"號客戶端關(guān)閉。");
channel.close();
}else {
channel.write(ByteBuffer.wrap(("客戶端,你傳過來的數(shù)據(jù)是:" + sb.toString()).getBytes()));
System.out.println(sb.toString()+"132123");
}
}
it.remove();
}
}
}
private static void init() throws Exception {
selector = Selector.open();
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(8081));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
}
參考連接
https://www.cnblogs.com/xdouby/p/8942083.html
https://www.zhihu.com/question/22524908
https://blog.csdn.net/ArtAndLife/article/details/121001656
AIO
JDK7 引入了 AsynchronousI/O,即 AIO。在進行 I/O 編程中,常用到兩種模式:Reactor 和 Proactor。Java 的 NIO 就是 Reactor,當(dāng)有事件觸發(fā)時,服務(wù)器端得到通知,進行相應(yīng)的處理 AIO 即 NIO2.0,叫做異步不阻塞的 IO。AIO 引入異步通道的概念,采用了 Proactor 模式,簡化了程序編寫,有效的請求才啟動線程,它的特點是先由操作系統(tǒng)完成后才通知服務(wù)端程序啟動線程去處理,一般適用于連接數(shù)較多且連接時間較長的應(yīng)用 目前 AIO 還沒有廣泛應(yīng)用,Netty 也是基于 NIO,而不是 AIO,因此我們就不詳解 AIO 了,有興趣的同學(xué)可以參考《Java新一代網(wǎng)絡(luò)編程模型AIO原理及Linux系統(tǒng)AIO介紹》
柚子快報激活碼778899分享:NIO 非阻塞式IO
推薦閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。