Java?NIO通信基礎(chǔ)示例詳解
Java NIO 通信基礎(chǔ)介紹
高性能的 Java 通信,絕對(duì)離不開 Java NIO 技術(shù),現(xiàn)在主流的技術(shù)框架或中間件服務(wù)器,都使 用了 Java NIO 技術(shù),譬如:Tomcat、Jetty、Netty。
Java NIO 由以下三個(gè)核心組件組成:
- Channel(通道)
- Buffer(緩沖區(qū))
- Selector(選擇器)
NIO 和 OIO 的對(duì)比
在 Java 中,NIO 和 OIO 的區(qū)別,主要體現(xiàn)在三個(gè)方面:
- OIO 是面向流(Stream Oriented)的,NIO 是面向緩沖區(qū)(Buffer Oriented)的。 何謂面向流,何謂面向緩沖區(qū)呢? OIO 是面向字節(jié)流或字符流的,在一般的 OIO 操作中,我們以流式的方式順序地從一個(gè)流中讀取一個(gè)或多個(gè)字節(jié),因此,我們不能隨意地改變讀取指針的位置。而在 NIO 操作中則不同,NIO 中引入了 Channel(通道)和 Buffer(緩沖區(qū))的概念。讀取和寫入,只需要從通道中讀取數(shù)據(jù)到緩沖區(qū)中,或?qū)?shù)據(jù)從緩沖區(qū)中寫入到通道中。NIO 不像 OIO 那樣是順序操作,可以隨意地讀取 Buffer 中任意位置的數(shù)據(jù)。
- OIO 的操作是阻塞的,而 NIO 的操作是非阻塞的。 NIO 如何做到非阻塞的呢?大家都知道,OIO 操作都是阻塞的,例如,我們調(diào)用一個(gè) read 方法讀取一個(gè)文件的內(nèi)容,那么調(diào)用 read 的線程會(huì)被阻塞住,直到 read 操作完成。 而在 NIO 的非阻塞模式中,當(dāng)我們調(diào)用 read 方法時(shí),如果此時(shí)有數(shù)據(jù),則 read 讀取數(shù)據(jù)并返回;如果此時(shí)沒有數(shù)據(jù),則 read 直接返回,而不會(huì)阻塞當(dāng)前線程。NIO 的非阻塞,是如何做到的呢?NIO 使用了通道和通道的多路復(fù)用技術(shù)。
- OIO 沒有選擇器(Selector)概念,而 NIO 有選擇器的概念。 NIO 的實(shí)現(xiàn),是基于底層的選擇器的系統(tǒng)調(diào)用。NIO 的選擇器,需要底層操作系統(tǒng)提供支持。 而 OIO 不需要用到選擇器。
使用 FileChannel 完成文件復(fù)制的實(shí)踐案例
import java.io.*; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class MyCopyFile { private File inFile; private File outFile; private FileInputStream fis = null; private FileOutputStream fos = null; private FileChannel fisChannel = null; private FileChannel fosChannel = null; //復(fù)制文件 public void copyFile(String srcPath, String destPath) throws IOException { try { inFile = new File(srcPath); outFile = new File(destPath); fis = new FileInputStream(inFile); fos = new FileOutputStream(outFile); fisChannel = fis.getChannel(); fosChannel = fos.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int length = -1; while ((length = fisChannel.read(buffer)) != -1) { buffer.flip(); int outLenth = 0; while ((outLenth = fosChannel.write(buffer)) != 0) { System.out.println("讀取的字節(jié)數(shù)為:" + outLenth); } buffer.clear(); } //強(qiáng)制刷新磁盤 fosChannel.force(true); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { fosChannel.close(); fos.close(); fisChannel.close(); fis.close(); } } public static void main(String[] args) throws IOException { MyCopyFiletest = new MyCopyFile(); String s1 = "D:\\maze.txt"; String s2 = "D:\\maze1.txt"; MyCopyFile.copyFile(s1, s2); } }
使用 DatagramChannel 數(shù)據(jù)包通道發(fā)送數(shù)據(jù)的實(shí)踐案例
功能:
獲取用戶的輸入數(shù)據(jù),通過 DatagramChannel 數(shù)據(jù)報(bào)通道,將數(shù)據(jù)發(fā)送到遠(yuǎn)程的服務(wù)器。
客戶端代碼:
public class Client { //Client發(fā)送信息 public void send() throws IOException { DatagramChannel dChannel = DatagramChannel.open(); dChannel.configureBlocking(false); ByteBuffer buf = ByteBuffer.allocate(1024); Scanner sc = new Scanner(System.in); while (sc.hasNext()) { String s = sc.nextLine(); buf.put(s.getBytes()); buf.flip(); dChannel.send(buf, new InetSocketAddress("127.0.0.1", 9999)); buf.clear(); } dChannel.close(); } public static void main(String[] args) throws IOException { new Client().send(); } }
服務(wù)端代碼:
public class Server { //服務(wù)端接收 用戶發(fā)來的信息 public void receive() throws IOException { DatagramChannel serverChannel = DatagramChannel.open(); //設(shè)置成非阻塞模式 serverChannel.configureBlocking(false); serverChannel.bind(new InetSocketAddress("127.0.0.1", 9999)); Selector selector = Selector.open(); serverChannel.register(selector, SelectionKey.OP_READ); while (selector.select() > 0) { Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); ByteBuffer buffer = ByteBuffer.allocate(1024); while (iterator.hasNext()) { SelectionKey next = iterator.next(); if (next.isReadable()) { SocketAddress receive = serverChannel.receive(buffer); buffer.flip(); String s = new String(buffer.array(), 0, buffer.limit()); System.out.println(s); buffer.clear(); } } iterator.remove(); } //關(guān)閉選擇器和通道 selector.close(); serverChannel.close(); } public static void main(String[] args) throws IOException { new Server().receive(); } }
使用 NIO 實(shí)現(xiàn) Discard 服務(wù)器的實(shí)踐案例
功能:
僅僅讀取客戶端通道的輸入數(shù)據(jù),讀取完成后直接關(guān)閉客戶端通道;并且讀取到的數(shù)據(jù)直接拋棄掉
Discard 服務(wù)器代碼:
public class SocketServerDemo { public void receive() throws IOException { //創(chuàng)建服務(wù)器的通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //設(shè)置為非阻塞模式 serverSocketChannel.configureBlocking(false); //開啟選擇器 Selector selector = Selector.open(); //綁定鏈接 serverSocketChannel.bind(new InetSocketAddress(9999)); //將通道的某個(gè)IO事件 注冊(cè)到選擇器上 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //輪詢所有就緒的IO事件 while (selector.select() > 0) { //逐個(gè)獲取IO事件 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); //逐個(gè)判斷該IO事件是否為想要的 while (iterator.hasNext()) { SelectionKey next = iterator.next(); if (next.isAcceptable()) { //如果為該事件為“連接就緒”事件,就獲取客戶端的鏈接 SocketChannel clientSocket = serverSocketChannel.accept(); //將客戶端的鏈接設(shè)置為非阻塞模式 clientSocket.configureBlocking(false); //將新的通道的可讀事件,注冊(cè)到選擇器上 clientSocket.register(selector, SelectionKey.OP_READ); } else if (next.isReadable()) { //若IO事件為“可讀事件”,讀取數(shù)據(jù) SocketChannel clientSocket = (SocketChannel) next.channel(); //創(chuàng)建緩沖區(qū) ByteBuffer buffer = ByteBuffer.allocate(1024); int length = 0; //讀取事件 讓后丟棄 while ((length = clientSocket.read(buffer)) > 0) { buffer.flip(); String s = new String(buffer.array(), 0, length); System.out.println(s); buffer.clear(); } clientSocket.close(); } //移除選擇鍵 iterator.remove(); } } serverSocketChannel.close(); } public static void main(String[] args) throws IOException { new SocketServerDemo().receive(); } }
客戶端的 DiscardClient 代碼:
public class SocketClientDemo { public void socketClient() throws IOException { SocketChannel clientSocket = SocketChannel.open(new InetSocketAddress(9999)); //切換成非阻塞模式 clientSocket.configureBlocking(false); //如果沒有連接完成 就一直鏈接 while (!clientSocket.finishConnect()){ } //執(zhí)行到這里說明已經(jīng)連接完成了 ByteBuffer buffer = ByteBuffer.allocate(1024); buffer.put("Hello SocketService".getBytes()); buffer.flip(); clientSocket.write(buffer); clientSocket.shutdownInput(); clientSocket.close(); } public static void main(String[] args) throws IOException { new SocketClientDemo().socketClient(); } }
與 Java OIO 相比,Java NIO 編程大致的特點(diǎn)如下:
(1)在 NIO 中,服務(wù)器接收新連接的工作,是異步進(jìn)行的。不像 Java 的 OIO 那樣,服務(wù)器監(jiān)聽連接,是同步的、阻塞的。NIO 可以通過選擇器(也可以說成:多路復(fù)用器),后續(xù)不斷地輪詢選擇器的選擇鍵集合,選擇新到來的連接。
(2)在 NIO 中,SocketChannel 傳輸通道的讀寫操作都是異步的。如果沒有可讀寫的數(shù)據(jù),負(fù)責(zé) IO 通信的線程不會(huì)同步等待。這樣,線程就可以處理其他連接的通道;不需要像 OIO 那樣,線程一直阻塞,等待所負(fù)責(zé)的連接可用為止。
(3)在 NIO 中,一個(gè)選擇器線程可以同時(shí)處理成千上萬個(gè)客戶端連接,性能不會(huì)隨著客戶端的增加而線性下降。
以上就是Java NIO通信基礎(chǔ)示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Java NIO通信基礎(chǔ)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
elasticsearch節(jié)點(diǎn)間通信的基礎(chǔ)transport啟動(dòng)過程
這篇文章主要為大家介紹了elasticsearch節(jié)點(diǎn)間通信的基礎(chǔ)transport啟動(dòng)過程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04Jenkins系統(tǒng)如何進(jìn)行數(shù)據(jù)備份
隨著我們的長(zhǎng)期使用,Jenkins系統(tǒng)中的內(nèi)容會(huì)越來越多,特別是一些配置相關(guān)的東西,不能有任何丟失。這個(gè)時(shí)候我們就需要定期備份我們的Jenkins系統(tǒng),避免一些誤操作不小心刪除了某些重要文件,本文就將介紹下Jenkins系統(tǒng)如何進(jìn)行數(shù)據(jù)備份2021-06-06深入解析Java的設(shè)計(jì)模式編程中單例模式的使用
這篇文章主要介紹了深入解析Java的設(shè)計(jì)模式編程中單例模式的使用,一般來說將單例模式分為餓漢式單例和懶漢式單例,需要的朋友可以參考下2016-02-02SpringBoot是如何使用SQL數(shù)據(jù)庫的?
今天給大家?guī)淼氖顷P(guān)于Springboot的相關(guān)知識(shí),文章圍繞著SpringBoot是如何使用SQL數(shù)據(jù)庫的展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06SpringBoot--- SpringSecurity進(jìn)行注銷權(quán)限控制的配置方法
這篇文章主要介紹了SpringBoot--- SpringSecurity進(jìn)行注銷,權(quán)限控制,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08Java文件分級(jí)目錄打包下載zip的實(shí)例代碼
這篇文章主要介紹了Java文件分級(jí)目錄打包下載zip的實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08SpringBoot 3.0 新特性內(nèi)置聲明式HTTP客戶端實(shí)例詳解
聲明式 http 客戶端主旨是使得編寫 java http 客戶端更容易,為了貫徹這個(gè)理念,采用了通過處理注解來自動(dòng)生成請(qǐng)求的方式,本文給大家詳解介紹SpringBoot 聲明式HTTP客戶端相關(guān)知識(shí),感興趣的朋友跟隨小編一起看看吧2022-12-12