java十分鐘速懂知識點——NIO

1、引子

    nio是java的IO框架裏邊十分重要的一部份內容,其最核心的就是提供了非阻塞IO的處理方式,最典型的應用場景就是處理網絡鏈接。不少同窗提起nio都能提及一二,可是細究其背後的原理、思想每每就開始背書,說來講去都是那麼幾句,其中很多人並不見的真的很理解。本人以前就屬於此類,看了不少書和博客,可是大多數都只是講了三件套和怎麼使用,不多會很細緻的講背後的思想,那本次咱們就來扒一扒吧。
    不少博客描述nio都是這麼說的:基於Reactor模式實現的多路非阻塞高性能的網絡IO。那麼咱們就從這個定義來分析,其中兩個關鍵點:多路非阻塞和Reactor模式。(原本想把高性能也算進去,可是後來想一想這個應該算前二者的結果)下邊咱們來分別搞懂這兩塊。html

2、網絡IO模型

    多路非阻塞其實準確的名字叫作IO多路複用模型,其是linux五種網絡模型之一,也是當前網絡編程最常使用的模型之一。至於詳細的介紹請參考博客:高性能IO模型淺析(這個裏邊只給出了4中,沒有信號驅動IO,但講的很贊,特別是圖),這裏僅做簡要介紹和對比:java

  • 阻塞IO:java中老的bio即是這種模式,在接到事件(數據到達、數據拷貝完成等)前程序需阻塞等待。優勢是編碼簡單,缺點是效率低,處理程序阻塞會致使cpu利用率很低。
  • 非阻塞IO:在未接到事件時處理程序一直主動輪詢,這樣處理程序無需阻塞,能夠在輪詢間歇去幹別的,可是輪詢會形成重複請求,一樣浪費資源。之前java中實現的的僞異步模式就是採用這種思想。
  • IO複用模型:增長了對socket的事件監聽器(selector),從而把處理程序和對應的socket事件解耦,所用的socket鏈接都註冊在監聽器,在等待階段只有監聽器會阻塞,處理線程從監聽器獲取事件對socket鏈接處理便可,並且一個處理線程能夠對應多個鏈接(前兩種通常都是一個socket鏈接起一個線程,這就是爲何叫複用),有點是節省資源,因爲處理程序可以被多個鏈接複用,所以少數的線程就能處理大量鏈接。缺點一樣由於複用,若是是大量費時處理的鏈接(如大量鏈接上傳大文件),很容易形成線程佔滿而致使新鏈接失敗。
  • 信號驅動IO模型:在數據準別階段無需阻塞,只需向系統註冊一個信號,在數據準備好後,系統會響應該信號。該模型依賴於系統實現,並且信號通訊使用比較麻煩,所以java中未有對應實現。
  • 異步IO:與信號驅動IO很相似,並且在數據拷貝階段(指數據從系統緩衝區拷貝至程序本身的緩衝區,其餘模型改階段程序都須要阻塞等待)一樣能夠異步處理。有點沒必要多說,效率很高,缺點是依賴系統底層實現。目前不少語言都提供該模型的實現,jdk1.7以後一樣在concurrent包中提供了。

    對比以上五種模型能夠知道,IO複用模型從效率和實現成本綜合而言目前是比較好的選擇,這就是java基於該模型實現nio的根本緣由。上邊提到了IO複用模型的實現思想,其實這種思想在其餘語言中早已實現(如C++中聽說流弊哄哄超10w行代碼的ACE,自適配通訊環境,就採用了該模型),而且提出了一個叫Reactor的設計模式。react

3、Reactor模

    Reactor模式,翻譯過來叫作反引器模式,其目的是在事件驅動的應用中,將一個請求的可以分離而且調度給應用程序。我相信大多數人都沒看明白前一句的意思(書仍是要背的),說白了就是對於一個請求的多個事件(如鏈接、讀寫等),通過這種模式的處理,可以區分出來,而且分別交給對應的處理模塊處理。廢話很少說,來看下一個簡圖:
linux

     能夠看到Reactor模式中組件有acceptor、dispatcher和handler(這裏只是拿一種實現作個例子,真實的實現各有不一樣),其中 acceptor中註冊了各種事件,當鏈接有新的事件過來時,其會將事件交給dispatcher進行分發;dispatcher綁定了事件和對應處理程序handler的映射關係,當接到新事件時其會把事件分發到對應handler;而handler 負責處理對應事件,這塊就是咱們的業務層了。
    從該模式咱們能夠發現,對於 acceptor、dispatcher咱們每每只須要一個線程做爲入口便可,由於其並不會有耗時處理,效率很高,而handler則根據須要起幾個線程便可(多數時候使用一個線程池實現),這正是IO複用模型 望的效果。
     下邊咱們會介紹NIO是如何實現該模式的,在此以前先介紹一下框架,其實除了NIO以外,基於JVM實現的還有其餘Reactor框架,正好最近OSC牽頭翻譯了對應文檔,有興趣的能夠看下: Reactor 指南

4、NIO

    NIO的細節就很少講了,這裏只介紹下三件套:
  • channel:管道,能夠看作對流的封裝,有點像pipe,不過其是全雙工的。其好處是屏蔽了底層細節,不用關心流對應的是文件仍是網絡,也不用關心鏈接怎麼處理的,並且全雙工,不用考慮輸入流或輸出流,你只用使用buffer對其進行讀寫就好了。
  • buffer:channel的好基友,底層就是個字節數組,不一樣的是對其進行了封裝,不只提供了對基本類型的支持,並且內部維持了讀寫位置(postion、limit、capacity、mark等),還提供了便捷的方法(clear、flip)。對channel的讀寫必須經過buffer。
  • selector:這個很少說了,若是前邊認真看基本上就明白乾啥的,就是Reactor模式中Acceptor的實現。

    再來看個簡圖吧: 編程

    基本上和Reactor能對應上,少了個dispatcher,這是因爲jdk自己提供的nio比較基本,dispatcher通常都由咱們本身實現,而在我理解中,mina、netty這些框架很重要的一方面也是提供了該部分的實現。
設計模式

5、一個例子

    從《netty權威指南》上抄了個例子以及配圖,並且代碼沒有客戶端的,你們能夠瞄一眼吧(爲何沒有?由於已經快一點了,我不想寫了......):
服務器端時序圖:數組

客戶端時序圖: 服務器

服務器端代碼:
網絡

package com.gj.netty.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * Created by guojing on 2015/6/7.
 */
public class MultiplexerTimerServer implements Runnable {

    private Selector selector;
    private ServerSocketChannel servChannel;
    private volatile boolean stop;

    public MultiplexerTimerServer(int port) {
        try {
            selector = Selector.open(); //新建多路複用selector
            servChannel = ServerSocketChannel.open();   //新建channel
            servChannel.configureBlocking(false);  //設置非阻塞
            servChannel.socket().bind(new InetSocketAddress(port),1024); //端口、塊大小
            servChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("TimeServer is start, port:" + port);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public void run() {
        while (!stop){
            try {
                selector.select(1000);
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> ketIt = keys.iterator();
                SelectionKey key = null;
                while (ketIt.hasNext()){
                    key = ketIt.next();
                    ketIt.remove();
                    //處理對應key事件
                    handler(key);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

    private void handler(SelectionKey key){
        //根據key去除channel作對應處理
    }
}
View Code

6、最後一點囉嗦

    我想若是這會兒還有人記得標題必定會罵我了,丫的十分鐘個屁啊,認真看完至少待半個小時。這個我只能說若是你以前已經理解了,那麼畫個10分鐘瞟一眼無所謂的,若是之前沒理解,若是本文能讓你有了更好的理解,那麼花多少時間更無所謂了,要知道懂了java的nio是量的積累,瞭解了其背後的思想和原理是質的積累。並且,我明明計劃半小時寫完的,這會已經2個多小時過去了......框架

相關文章
相關標籤/搜索