Java 非阻塞I/O使用方法
絕大部分知識(shí)與實(shí)例來自O(shè)'REILLY的《Java網(wǎng)絡(luò)編程》(Java Network Programming,Fourth Edition,by Elliotte Rusty Harold(O'REILLY))。
非阻塞I/O簡(jiǎn)介
非阻塞I/O(NIO)是處理高并發(fā)的一種手段。在高并發(fā)的情況下,創(chuàng)建和回收線程以及在線程間切換的開銷變得不容忽視,此時(shí)就可以使用非阻塞I/O技術(shù)。這種技術(shù)的核心思想是每次選取一個(gè)準(zhǔn)備好的連接,盡快地填充這個(gè)連接所能管理的盡可能多的數(shù)據(jù),然后轉(zhuǎn)向下一個(gè)準(zhǔn)備好的連接。
利用非阻塞I/O實(shí)現(xiàn)的客戶端
一般情況下,客戶端不會(huì)需要處理很高數(shù)量的并發(fā)連接。事實(shí)上,非阻塞I/O主要是為服務(wù)器設(shè)計(jì)的,但它也可以用在客戶端上。由于客戶端的設(shè)計(jì)相比服務(wù)器容易,因此下面先用客戶端來進(jìn)行簡(jiǎn)單演示。
首先介紹通道(channel)和緩沖區(qū)。非阻塞I/O中使用SocketChannel類創(chuàng)建連接。要獲取SocketChannel對(duì)象,需要將一個(gè)SocketAddress對(duì)象(通常會(huì)使用它的子類InetSocketAddress)傳入它的靜態(tài)工廠方法open()中。下面為一個(gè)示例:
SocketAddress address = new InetSocketAddress("127.0.0.1", 19); SocketChannel client = SocketChannel.open(address);
open()方法是阻塞的,因此這之后的代碼在連接建立之前不會(huì)執(zhí)行。如果連接無(wú)法建立,會(huì)拋出一個(gè)IOException異常。
連接建立之后就需要獲取輸入和輸出。不同于傳統(tǒng)的getInputStream()與getOutputStream(),利用通道,你可以直接寫入通道本身。不是寫入字節(jié)數(shù)組,而是要寫入一個(gè)ByteBuffer對(duì)象。ByteBuffer對(duì)象通過ByteBuffer.allocate(int capacity)獲取,capacity為緩沖區(qū)大小,單位為字節(jié):
ByteBuffer buffer = ByteBuffer.allocate(74);
獲得ByteBuffer對(duì)象后,將其傳遞給SocketChannel對(duì)象的read()方法,SocketChannel對(duì)象會(huì)用從Socket讀取的數(shù)據(jù)填充這個(gè)緩沖區(qū)。read()方法返回成功讀取并儲(chǔ)存在緩沖區(qū)中的字節(jié)數(shù)。默認(rèn)情況下,它會(huì)至少讀取一個(gè)字節(jié),或者返回-1指示數(shù)據(jù)結(jié)束,沒有字節(jié)可用時(shí)阻塞。這與InputStream的行為大致相同。但如果設(shè)置成非阻塞模式,沒有字節(jié)可用時(shí)它會(huì)立即返回0,不會(huì)阻塞。
現(xiàn)在假定緩沖區(qū)內(nèi)已經(jīng)有了一些數(shù)據(jù),之后就需要將它們提取出來??梢允褂脗鹘y(tǒng)的方式,先將數(shù)據(jù)寫入一個(gè)字節(jié)數(shù)組,之后再寫入一個(gè)輸出流中。這里介紹一種完全基于通道的方法:利用Channels工具類將輸出流封裝到一個(gè)通道中:
WritableByteChannel out = Channels.newChannel(System.out);
上面的代碼將System.out封裝入一個(gè)通道中。這之后就可以進(jìn)行輸出了。ByteBuffer對(duì)象在每次輸出之前,需要調(diào)用一下它的flip()方法,使得通道從開頭開始讀。在讀寫完畢后,還需要調(diào)用它的clear()方法,重置緩沖區(qū)的狀態(tài)。下面是進(jìn)行一次數(shù)據(jù)輸出的代碼:
buffer.flip(); out.write(buffer); buffer.clear();
實(shí)例1:利用非阻塞I/O實(shí)現(xiàn)的CharGenerator(字符生成器)客戶端
服務(wù)器代碼:
public static void createCharGeneratorServer(){ try(ServerSocket server = new ServerSocket(19)){ while(true){ try(Socket connection = server.accept()){ OutputStream out = connection.getOutputStream(); int firstPrintableCharacter = 33; int numberOfPrintableCharacter = 94; int numberOfCharactersPerLine = 72; int start = firstPrintableCharacter; while(true){ for(int i = start ; i < start + numberOfCharactersPerLine ; i++){ out.write (firstPrintableCharacter + (i - firstPrintableCharacter) % numberOfPrintableCharacter); } out.write('\r'); out.write('\n'); start = firstPrintableCharacter + (start + 1 - firstPrintableCharacter) % numberOfPrintableCharacter; } }catch (IOException e) { e.printStackTrace(); } } } catch (IOException e) { e.printStackTrace(); } }
客戶端代碼:
try { SocketAddress address = new InetSocketAddress("127.0.0.1", 19); SocketChannel client = SocketChannel.open(address); ByteBuffer buffer = ByteBuffer.allocate(74); WritableByteChannel out = Channels.newChannel(System.out); while(client.read(buffer) != -1){ buffer.flip(); out.write(buffer); buffer.clear(); } } catch (IOException e) { e.printStackTrace(); } 輸出(無(wú)限循環(huán)): ]^_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEF ^_`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFG _`abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGH `abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHI abcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJ bcdefghijklmnopqrstuvwxyz{|}~!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJK
啟用非阻塞模式
上面的程序和使用輸入/輸出流的傳統(tǒng)方式并沒有太大差別。不過,可以調(diào)用ServerSocket的configureBlocking(false)方法將其設(shè)置為非阻塞模式。這個(gè)模式下,如果沒有可用的數(shù)據(jù),read()方法會(huì)立即返回,這讓客戶端可以去做其他事情。不過,由于read()方法在讀不到數(shù)據(jù)時(shí)會(huì)返回0,讀取數(shù)據(jù)的循環(huán)需要做一些改動(dòng):
while(true){ //這里可以寫每次循環(huán)都要做的事,無(wú)論有沒有讀到數(shù)據(jù) int n = client.read(buffer); if(n > 0){ buffer.flip(); out.write(buffer); buffer.clear(); }else if (n == -1) { //除非服務(wù)器故障,否則不會(huì)發(fā)生 break; } }
總結(jié)
以上就是本文關(guān)于Java 非阻塞I/O使用方法的全部?jī)?nèi)容,希望對(duì)大家有所幫助。歡迎各位參閱:Java網(wǎng)絡(luò)編程基礎(chǔ)篇之單向通信, Java使用代理進(jìn)行網(wǎng)絡(luò)連接方法示例等,有什么問題可以隨時(shí)留言,小編會(huì)及時(shí)回復(fù)大家。感謝大家對(duì)本站的支持!
相關(guān)文章
java 數(shù)據(jù)庫(kù)連接與增刪改查操作實(shí)例詳解
這篇文章主要介紹了java 數(shù)據(jù)庫(kù)連接與增刪改查操作,結(jié)合實(shí)例形式詳細(xì)分析了java使用jdbc進(jìn)行數(shù)據(jù)庫(kù)連接及增刪改查等相關(guān)操作實(shí)現(xiàn)技巧與注意事項(xiàng),需要的朋友可以參考下2019-11-11SpringBoot自動(dòng)配置特點(diǎn)與原理詳細(xì)分析
這篇文章主要介紹了SpringBoot自動(dòng)配置原理分析,SpringBoot是我們經(jīng)常使用的框架,那么你能不能針對(duì)SpringBoot實(shí)現(xiàn)自動(dòng)配置做一個(gè)詳細(xì)的介紹。如果可以的話,能不能畫一下實(shí)現(xiàn)自動(dòng)配置的流程圖。牽扯到哪些關(guān)鍵類,以及哪些關(guān)鍵點(diǎn)2022-08-08SSM項(xiàng)目中使用攔截器和過濾器的實(shí)現(xiàn)示例
這篇文章主要介紹了SSM項(xiàng)目中使用攔截器和過濾器的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04Spring Data + Thymeleaf 3 + Bo
本篇文章主要介紹了Spring Data + Thymeleaf 3 + Bootstrap 4 實(shí)現(xiàn)分頁(yè)器實(shí)例代碼,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05Java實(shí)現(xiàn)銀行賬戶管理子系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)銀行賬戶管理子系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05詳解如何使用tldb數(shù)據(jù)庫(kù)的java客戶端
這篇文章主要為大家介紹了如何使用tldb數(shù)據(jù)庫(kù)的java客戶端過程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09設(shè)計(jì)模式之構(gòu)建(Builder)模式 建造房子實(shí)例分析
構(gòu)建模式主要用來針對(duì)復(fù)雜產(chǎn)品生產(chǎn),分離部件構(gòu)建細(xì)節(jié),以達(dá)到良好的伸縮性,考慮到設(shè)計(jì)模式來源于建筑學(xué),因此舉一個(gè)建造房子的例子,需要的朋友可以參考下2012-12-12Idea中maven項(xiàng)目實(shí)現(xiàn)登錄驗(yàn)證碼功能
這篇文章主要介紹了Idea中maven項(xiàng)目實(shí)現(xiàn)登錄驗(yàn)證碼功能,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12解決IDEA?JDK9沒有module-info.java的問題
這篇文章主要介紹了解決IDEA?JDK9沒有module-info.java的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01