小白也可以學(xué)會(huì)的Java NIO的Write事件
一、NIO Server端
1.1 多路復(fù)用開發(fā)一般步驟
//打開選擇器 Selector selector = Selector.open(); //打開通到 ServerSocketChannel socketChannel = ServerSocketChannel.open(); //配置非阻塞模型 socketChannel.configureBlocking(false); //綁定端口 socketChannel.bind(new InetSocketAddress(8080)); //注冊(cè)事件,OP_ACCEPT只適用于ServerSocketChannel socketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { selector.select(); Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iter = selectionKeys.iterator(); while(iter.hasNext()) { SelectionKey key = iter.next(); if(key.isAcceptable()) { SocketChannel channel = ((ServerSocketChannel)key.channel()).accept(); channel.configureBlocking(false); channel.register(selector,SelectionKey.OP_READ); } if(key.isWritable()) { } if(key.isReadable()) { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer readBuffer = ByteBuffer.allocate(1024); channel.read(readBuffer); readBuffer.flip(); // handler Buffer // 一般是響應(yīng)客戶端的數(shù)據(jù) // 直接是write寫不就完事了嘛,為啥需要write事件? // channel.write(...) } iter.remove(); } }
1.2 解惑寫事件
對(duì)NIO的寫操作:
- 為什么要注冊(cè)寫事件
- 何時(shí)注冊(cè)寫事件
- 為什么寫完之后要取消注冊(cè)寫事件
如果有channel在Selector上注冊(cè)了SelectionKey.OP_WRITE
,在調(diào)用selector.select();時(shí),系統(tǒng)會(huì)檢查內(nèi)核寫緩沖區(qū)是否可寫:
- 如果可寫,
selector.select();
立即返回,進(jìn)入key.isWritable()
- 何時(shí)不可寫?比如緩沖區(qū)已滿,channel調(diào)用了shutdownOutPut等
當(dāng)然除了注冊(cè)寫事件,你也可以在channel直接調(diào)用write(…),也可以將數(shù)據(jù)發(fā)出去,但這樣不夠靈活,而且可能浪費(fèi)CPU。
比如服務(wù)端需要發(fā)送一個(gè)200M的Buffer,看看是否使用OP_WRITE事件的區(qū)別。
二、不使用事件
程序運(yùn)行到這會(huì)等到200M文件發(fā)送完成后才繼續(xù)往下執(zhí)行,不符合異步事件模型的思想。若緩沖區(qū)一直處不可寫狀態(tài),則該過(guò)程一直在這里死循環(huán),浪費(fèi)CPU。
// 200M的Buffer ByteBuffer buffer = .... while(buffer.hasRemaining()) { // 該方法只會(huì)寫入小于socket's output buffer空閑區(qū)域的任何字節(jié)數(shù) // 并返回寫入的字節(jié)數(shù),可能是0字節(jié) channel.write(buffer); }
三、使用事件
if(key.isReadable()) { // 200M Buffer ByteBuffer buffer = .... // 注冊(cè)寫事件 key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); // 綁定Buffer key.attach(buffer); } // 可寫分支 if(key.isWritable()) { ByteBuffer buffer = (ByteBuffer) key.attachment(); SocketChannel channel = (SocketChannel) key.channel(); if (buffer.hasRemaining()) { channel.write(buffer) } else { // 發(fā)送完了就取消寫事件,否則下次還會(huì)進(jìn)入寫事件分支(因?yàn)橹灰€可寫,就會(huì)進(jìn)入) key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE); } }
要觸發(fā)寫事件,需要先向 selector 注冊(cè)該通道的寫事件,跟注冊(cè)讀事件一樣,當(dāng)?shù)讓訉懢彌_區(qū)有空閑就會(huì)觸發(fā)寫事件了,而一般來(lái)說(shuō)底層的寫緩沖區(qū)大部分都是空閑的。所以一般只要注冊(cè)了寫事件,就會(huì)立馬觸發(fā)了,為了避免 cpu 空轉(zhuǎn),在寫操作完成后需要把寫事件取消掉,然后下次再有寫操作時(shí)重新注冊(cè)寫事件。
四、NIO Client端
開發(fā)的一般步驟
// 打開選擇器 Selector selector = Selector.open(); // 打開通道 SocketChannel socketChannel = SocketChannel.open(); // 配置非阻塞模型 socketChannel.configureBlocking(false); // 連接Server socketChannel.connect(new InetSocketAddress("127.0.0.1",8080)); // 注冊(cè)事件 socketChannel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ); // 循環(huán)處理 while (true) { selector.select(); Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> iter = keys.iterator(); while(iter.hasNext()) { SelectionKey key = iter.next(); if(key.isConnectable()) { // 連接建立或者連接建立不成功 SocketChannel channel = (SocketChannel) key.channel(); // 完成連接建立 if(channel.finishConnect()) { } } if(key.isReadable()) { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(500 * 1024 * 1024); buffer.clear(); channel.read(buffer); // buffer Handler } iter.remove(); } }
起初對(duì)OP_CONNECT事件還有finishConnect不理解,OP_CONNECT事件何時(shí)觸發(fā),特別是為什么要在key.isConnectable()分支里調(diào)用finishConnect方法后才能進(jìn)行讀寫操作。
首先,在non-blocking模式下調(diào)用socketChannel.connect(new InetSocketAddress(“127.0.0.1”,8080));連接遠(yuǎn)程主機(jī),如果連接能立即建立就像本地連接一樣,該方法會(huì)立即返回true,否則該方法會(huì)立即返回false,然后系統(tǒng)底層進(jìn)行三次握手建立連接。連接有兩種結(jié)果,一種是成功連接,第二種是異常,但是connect方法已經(jīng)返回,無(wú)法通過(guò)該方法的返回值或者是異常來(lái)通知用戶程序建立連接的情況,所以由OP_CONNECT事件和finishConnect方法來(lái)通知用戶程序。不管系統(tǒng)底層三次連接是否成功,selector都會(huì)被喚醒繼而觸發(fā)OP_CONNECT事件,如果握手成功,并且該連接未被其他線程關(guān)閉,finishConnect會(huì)返回true,然后就可以順利的進(jìn)行channle讀寫。如果網(wǎng)絡(luò)故障,或者遠(yuǎn)程主機(jī)故障,握手不成功,用戶程序可以通過(guò)finishConnect方法獲得底層的異常通知,進(jìn)而處理異常。
到此這篇關(guān)于小白也可以學(xué)會(huì)的Java NIO的Write事件的文章就介紹到這了,更多相關(guān)Java NIO的Write事件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot實(shí)現(xiàn)獲取當(dāng)前服務(wù)器IP及當(dāng)前項(xiàng)目使用的端口號(hào)Port
這篇文章主要介紹了springboot實(shí)現(xiàn)獲取當(dāng)前服務(wù)器IP及當(dāng)前項(xiàng)目使用的端口號(hào)Port方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12SpringBoot項(xiàng)目如何訪問(wèn)jsp頁(yè)面的示例代碼
本篇文章主要介紹了SpringBoot項(xiàng)目如何訪問(wèn)jsp頁(yè)面的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10淺析JAVA常用JDBC連接數(shù)據(jù)庫(kù)的方法總結(jié)
本篇文章是對(duì)在JAVA中常用JDBC連接數(shù)據(jù)庫(kù)的方法進(jìn)行了詳細(xì)的總結(jié)分析,需要的朋友參考下2013-07-07JAVA將中文轉(zhuǎn)換為拼音簡(jiǎn)單實(shí)現(xiàn)方法
拼音轉(zhuǎn)換是中文處理的常見需求,TinyPinyin、HanLP、pinyin4j是常用的本地拼音轉(zhuǎn)換庫(kù),各有特點(diǎn),開發(fā)者可根據(jù)具體需求選擇合適的拼音轉(zhuǎn)換工具,需要的朋友可以參考下2024-10-10Spring Boot 開發(fā)環(huán)境熱部署詳細(xì)教程
這篇文章主要介紹了Spring Boot 開發(fā)環(huán)境熱部署,本文給大家介紹了Spring Boot 開發(fā)環(huán)境熱部署的原理及快速配置方法,通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06SpringSecurity導(dǎo)致SpringBoot跨域失效的問(wèn)題解決
本文主要介紹了SpringSecurity導(dǎo)致SpringBoot跨域失效的問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01