溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java NIO使用及原理分析是什么

發(fā)布時間:2021-12-03 17:27:18 來源:億速云 閱讀:137 作者:柒染 欄目:云計算

Java NIO使用及原理分析是什么,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。

終于可以進入NIO中最有意思的部分非阻塞I/O。通常在進行同步I/O操作時,如果讀取數(shù)據(jù),代碼會阻塞直至有 可供讀取的數(shù)據(jù)。同樣,寫入調(diào)用將會阻塞直至數(shù)據(jù)能夠?qū)懭?。傳統(tǒng)的Server/Client模式會基于TPR(Thread per Request),服務(wù)器會為每個客戶端請求建立一個線程,由該線程單獨負責處理一個客戶請求。這種模式帶來的一個問題就是線程數(shù)量的劇增,大量的線程會增大服務(wù)器的開銷。大多數(shù)的實現(xiàn)為了避免這個問題,都采用了線程池模型,并設(shè)置線程池線程的最大數(shù)量,這由帶來了新的問題,如果線程池中有200個線程,而有200個用戶都在進行大文件下載,會導(dǎo)致第201個用戶的請求無法及時處理,即便第201個用戶只想請求一個幾KB大小的頁面。傳統(tǒng)的 Server/Client模式如下圖所示:

Java NIO使用及原理分析是什么

NIO中非阻塞I/O采用了基于Reactor模式的工作方式,I/O調(diào)用不會被阻塞,相反是注冊感興趣的特定I/O事件,如可讀數(shù)據(jù)到達,新的套接字連接等等,在發(fā)生特定事件時,系統(tǒng)再通知我們。NIO中實現(xiàn)非阻塞I/O的核心對象就是Selector,Selector就是注冊各種I/O事件地 方,而且當那些事件發(fā)生時,就是這個對象告訴我們所發(fā)生的事件,如下圖所示:

從圖中可以看出,當有讀或?qū)懙热魏巫缘氖录l(fā)生時,可以從Selector中獲得相應(yīng)的SelectionKey,同時從 SelectionKey中可以找到發(fā)生的事件和該事件所發(fā)生的具體的SelectableChannel,以獲得客戶端發(fā)送過來的數(shù)據(jù)。關(guān)于 SelectableChannel的可以參考Java NIO使用及原理分析(一)

使用NIO中非阻塞I/O編寫服務(wù)器處理程序,大體上可以分為下面三個步驟:

1. 向Selector對象注冊感興趣的事件 
2. 從Selector中獲取感興趣的事件 
3. 根據(jù)不同的事件進行相應(yīng)的處理

接下來我們用一個簡單的示例來說明整個過程。首先是向Selector對象注冊感興趣的事件:

[java] view plain copy

 print?

  1. /* 

  2.  * 注冊事件 

  3.  * */  

  4. protected Selector getSelector() throws IOException {  

  5.     // 創(chuàng)建Selector對象  

  6.     Selector sel = Selector.open();  

  7.       

  8.     // 創(chuàng)建可選擇通道,并配置為非阻塞模式  

  9.     ServerSocketChannel server = ServerSocketChannel.open();  

  10.     server.configureBlocking(false);  

  11.       

  12.     // 綁定通道到指定端口  

  13.     ServerSocket socket = server.socket();  

  14.     InetSocketAddress address = new InetSocketAddress(port);  

  15.     socket.bind(address);  

  16.       

  17.     // 向Selector中注冊感興趣的事件  

  18.     server.register(sel, SelectionKey.OP_ACCEPT);   

  19.     return sel;  

  20. }  

創(chuàng)建了ServerSocketChannel對象,并調(diào)用configureBlocking()方法,配置為非阻塞模式,接下來的三行代碼把該通道綁定到指定端口,最后向Selector中注冊事件,此處指定的是參數(shù)是OP_ACCEPT,即指定我們想要監(jiān)聽accept事件,也就是新的連接發(fā) 生時所產(chǎn)生的事件,對于ServerSocketChannel通道來說,我們唯一可以指定的參數(shù)就是OP_ACCEPT。

從Selector中獲取感興趣的事件,即開始監(jiān)聽,進入內(nèi)部循環(huán):

[java] view plain copy

 print?

  1. /* 

  2.  * 開始監(jiān)聽 

  3.  * */   

  4. public void listen() {   

  5.     System.out.println("listen on " + port);  

  6.     try {   

  7.         while(true) {   

  8.             // 該調(diào)用會阻塞,直到至少有一個事件發(fā)生  

  9.             selector.select();   

  10.             Set<SelectionKey> keys = selector.selectedKeys();  

  11.             Iterator<SelectionKey> iter = keys.iterator();  

  12.             while (iter.hasNext()) {   

  13.                 SelectionKey key = (SelectionKey) iter.next();   

  14.                 iter.remove();   

  15.                 process(key);   

  16.             }   

  17.         }   

  18.     } catch (IOException e) {   

  19.         e.printStackTrace();  

  20.     }   

  21. }  

在非阻塞I/O中,內(nèi)部循環(huán)模式基本都是遵循這種方式。首先調(diào)用select()方法,該方法會阻塞,直到至少有一個事件發(fā)生,然后再使用selectedKeys()方法獲取發(fā)生事件的SelectionKey,再使用迭代器進行循環(huán)。

最后一步就是根據(jù)不同的事件,編寫相應(yīng)的處理代碼:

[java] view plain copy

 print?

  1. /* 

  2.  * 根據(jù)不同的事件做處理 

  3.  * */  

  4. protected void process(SelectionKey key) throws IOException{  

  5.     // 接收請求  

  6.     if (key.isAcceptable()) {  

  7.         ServerSocketChannel server = (ServerSocketChannel) key.channel();  

  8.         SocketChannel channel = server.accept();  

  9.         channel.configureBlocking(false);  

  10.         channel.register(selector, SelectionKey.OP_READ);  

  11.     }  

  12.     // 讀信息  

  13.     else if (key.isReadable()) {  

  14.         SocketChannel channel = (SocketChannel) key.channel();   

  15.         int count = channel.read(buffer);   

  16.         if (count > 0) {   

  17.             buffer.flip();   

  18.             CharBuffer charBuffer = decoder.decode(buffer);   

  19.             name = charBuffer.toString();   

  20.             SelectionKey sKey = channel.register(selector, SelectionKey.OP_WRITE);   

  21.             sKey.attach(name);   

  22.         } else {   

  23.             channel.close();   

  24.         }   

  25.         buffer.clear();   

  26.     }  

  27.     // 寫事件  

  28.     else if (key.isWritable()) {  

  29.         SocketChannel channel = (SocketChannel) key.channel();   

  30.         String name = (String) key.attachment();   

  31.           

  32.         ByteBuffer block = encoder.encode(CharBuffer.wrap("Hello " + name));   

  33.         if(block != null)  

  34.         {  

  35.             channel.write(block);  

  36.         }  

  37.         else  

  38.         {  

  39.             channel.close();  

  40.         }  

  41.   

  42.      }  

  43. }  

此處分別判斷是接受請求、讀數(shù)據(jù)還是寫事件,分別作不同的處理。

看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI