Java?BOI與NIO超詳細(xì)實(shí)例精講
Java BIO
阻塞IO,每個(gè)客戶端鏈接都需要一個(gè)獨(dú)立的線程處理,客戶端鏈接沒(méi)關(guān)閉時(shí),線程鏈接處于阻塞狀態(tài),直到客戶端鏈接關(guān)閉
如果客戶端鏈接沒(méi)有讀取到數(shù)據(jù),鏈接就會(huì)一直阻塞住,造成資源浪費(fèi)
示例代碼
開(kāi)發(fā)一個(gè)ServerSocket服務(wù)端,通過(guò)telnet鏈接發(fā)送信息
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; public class BIOServerTest { public static void main(String[] args) throws Exception { ExecutorService pool = Executors.newCachedThreadPool(); ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服務(wù)端啟動(dòng)"); while (true) { System.out.println("等待鏈接..."); Socket socket = serverSocket.accept(); pool.execute(() -> { handler(socket); }); } } private static void handler(Socket socket) { try { System.out.println("有一個(gè)客戶端接入:" + Thread.currentThread().getName()); byte[] bytes = new byte[1024]; //通過(guò)socket 獲取輸入流 InputStream inputStream = socket.getInputStream(); //循環(huán)讀取客戶端發(fā)送的數(shù)據(jù) while (true) { System.out.println("等待讀取數(shù)據(jù)..."); int read = inputStream.read(bytes); if (read != -1) { //輸出客戶端讀取的數(shù)據(jù) System.out.println("線程信息:" + Thread.currentThread().getName()); System.out.println(new String(bytes, 0, read)); } else { break; } } } catch (Exception e) { e.printStackTrace(); } finally { System.out.println("關(guān)閉client鏈接:" + Thread.currentThread().getName()); try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
通過(guò)telnet鏈接兩個(gè)客戶端,分別發(fā)送請(qǐng)求
從控制臺(tái)打印信息可以看出每一個(gè)鏈接對(duì)應(yīng)的線程都是獨(dú)立的
等待鏈接...
有一個(gè)客戶端接入:pool-1-thread-1
等待讀取數(shù)據(jù)...
線程信息:pool-1-thread-1
aaa
等待讀取數(shù)據(jù)...
線程信息:pool-1-thread-1
bbb
等待讀取數(shù)據(jù)...
等待鏈接...
有一個(gè)客戶端接入:pool-1-thread-2
等待讀取數(shù)據(jù)...
線程信息:pool-1-thread-2
ccc
等待讀取數(shù)據(jù)...
線程信息:pool-1-thread-2
ddd
等待讀取數(shù)據(jù)...
Java NIO
非阻塞IO,通過(guò)Selector和Channel,實(shí)現(xiàn)一個(gè)線程維護(hù)多個(gè)鏈接
NIO有三大核心部分:Channel(通道),Buffer(緩沖區(qū)),Selector(選擇器)
NOI是面向緩沖區(qū)編程的,數(shù)據(jù)讀取到一個(gè)它稍后處理的緩沖區(qū),需要時(shí)可在緩沖區(qū)中前后移動(dòng),增加的處理過(guò)程的靈活性,使用它可以提供非阻塞式的高伸縮性網(wǎng)絡(luò)
NIO是通過(guò)事件驅(qū)動(dòng)編程的,Selector會(huì)根據(jù)不同的事件,在各個(gè)通道上切換
Channel同時(shí)支持讀寫(xiě)雙向處理
Selector能夠監(jiān)測(cè)到多個(gè)注冊(cè)的通到是否有事件發(fā)生,這樣就可以只用一個(gè)線程去管理多個(gè)通道,也就是管理多個(gè)鏈接和請(qǐng)求。
只有在鏈接真正有讀寫(xiě)事件發(fā)生時(shí),才會(huì)進(jìn)行讀寫(xiě),大大減少了系統(tǒng)開(kāi)銷(xiāo),并且不必為每個(gè)鏈接創(chuàng)建一個(gè)線程
代碼解讀
- 當(dāng)客戶端鏈接時(shí),會(huì)通過(guò)ServerSocketChannel得到SocketChannel
- 將SocketChannel注冊(cè)到Selector上,一個(gè)selector上可以注冊(cè)多個(gè)SocketChannel
- 通過(guò)socketChannel.register()方法注冊(cè)
- 注冊(cè)后返回一個(gè)SelectionKey,會(huì)和該Selector關(guān)聯(lián)
- Selector監(jiān)聽(tīng)select方法,返回有事件發(fā)生的通道的個(gè)數(shù)
- 進(jìn)一步得到有事件發(fā)生的各個(gè)SelectionKey,通過(guò)SelectionKey反向獲取SocketChannel
- 通過(guò)得到的channel完成業(yè)務(wù)處理
1)服務(wù)端代碼
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; public class NIOServer { public static void main(String[] args) throws Exception { ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); InetSocketAddress inetSocketAddress = new InetSocketAddress(7000); serverSocketChannel.socket().bind(inetSocketAddress); serverSocketChannel.configureBlocking(false); Selector selector = Selector.open(); //注冊(cè)客戶端鏈接事件到selector serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { int select = selector.select(1000L); if (select == 0) { System.out.println("等待1秒,沒(méi)有事件發(fā)生"); continue; } //監(jiān)聽(tīng)到相關(guān)事件發(fā)生,讀取SelectionKey集合,遍歷處理所有事件 Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isAcceptable()) { SocketChannel socketChannel = serverSocketChannel.accept(); System.out.println("客戶端鏈接成功,生成一個(gè)sockeChannel " + socketChannel.hashCode()); //將socketChannel設(shè)置為非阻塞 socketChannel.configureBlocking(false); //注冊(cè)內(nèi)容讀取事件到selector,同時(shí)關(guān)聯(lián)一個(gè)Buffer socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024)); } if (key.isReadable()) { SocketChannel channel = null; try { channel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = (ByteBuffer) key.attachment(); int read = channel.read(byteBuffer); System.out.println("讀取到數(shù)據(jù): " + new String(byteBuffer.array(), 0, read)); } catch (Exception e) { if (channel != null) { channel.close(); } e.printStackTrace(); } } //手動(dòng)從集合中移除當(dāng)前的SelectionKey,防止重復(fù)操作 keyIterator.remove(); } } } }
2)客戶端代碼
import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; public class NIOClient { public static void main(String[] args) throws Exception { SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 7000); if (!socketChannel.connect(inetSocketAddress)){ while (!socketChannel.finishConnect()){ System.out.println("鏈接未完成,客戶端不會(huì)阻塞...."); } } String str = "Hello,你好~~~"; ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes()); socketChannel.write(byteBuffer); System.in.read(); } }
控制臺(tái)輸出
等待1秒,沒(méi)有事件發(fā)生
等待1秒,沒(méi)有事件發(fā)生
等待1秒,沒(méi)有事件發(fā)生
等待1秒,沒(méi)有事件發(fā)生
等待1秒,沒(méi)有事件發(fā)生
客戶端鏈接成功,生成一個(gè)sockeChannel 60559178
讀取到數(shù)據(jù): Hello,你好~~~
等待1秒,沒(méi)有事件發(fā)生
到此這篇關(guān)于Java BOI與NIO超詳細(xì)實(shí)例精講的文章就介紹到這了,更多相關(guān)Java BOI與NIO內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用IDEA如何打包發(fā)布SpringBoot并部署到云服務(wù)器
這篇文章主要介紹了使用IDEA如何打包發(fā)布SpringBoot并部署到云服務(wù)器問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12SpringCloud之監(jiān)控?cái)?shù)據(jù)聚合Turbine的實(shí)現(xiàn)
這篇文章主要介紹了SpringCloud之監(jiān)控?cái)?shù)據(jù)聚合Turbine的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08Java8函數(shù)式接口的基礎(chǔ)學(xué)習(xí)教程
這篇文章主要給大家介紹了關(guān)于Java8函數(shù)式接口基礎(chǔ)學(xué)習(xí)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04排查Java應(yīng)用內(nèi)存泄漏問(wèn)題的步驟
這篇文章主要介紹了排查Java應(yīng)用內(nèi)存泄漏問(wèn)題的步驟,幫助大家更好的理解和學(xué)習(xí)Java,感興趣的朋友可以了解下2020-11-11一篇文章帶你解決 IDEA 每次新建項(xiàng)目 maven home directory 總是改變的問(wèn)題
這篇文章主要介紹了一篇文章帶你解決 IDEA 每次新建項(xiàng)目 maven home directory 總是改變的問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09Java Spring事務(wù)使用及驗(yàn)證過(guò)程詳解
這篇文章主要介紹了Java Spring事務(wù)使用及驗(yàn)證過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-12-12使用Jenkins一鍵打包部署SpringBoot項(xiàng)目的步驟詳解
任何簡(jiǎn)單操作的背后,都有一套相當(dāng)復(fù)雜的機(jī)制,本文將以SpringBoot應(yīng)用的在Docker環(huán)境下的打包部署為例,詳細(xì)講解如何使用Jenkins一鍵打包部署SpringBoot應(yīng)用,文中通過(guò)圖文結(jié)合講解的非常詳細(xì),需要的朋友可以參考下2023-11-11java雙向循環(huán)鏈表的實(shí)現(xiàn)代碼
這篇文章介紹了java雙向循環(huán)鏈表的實(shí)現(xiàn)代碼,有需要的朋友可以參考一下2013-09-09