NIO網絡相關基礎知識

前面的文章NIO基礎知識介紹了Java NIO的一些基本的類及功能說明,Java NIO是用來替換java 傳統IO的,NIO的一些新的特性在網絡交互方面會更加的明顯。java

Java 傳統IO的弊端

    「基於JVM來實現每一個通道的輪詢檢查通道狀態的方法是可行的,但仍然是有問題的,檢查每一個通道是否就緒是至少須要一次系統調用,執行的代價是很是昂貴的。同時這種檢查不是原子的。列表中的每一個通道在檢查以後狀態變成就緒,但須要等到下一次輪詢以前JVM是沒法感知的。最糟糕的是,JVM除了不斷遍歷列表以外將別無選擇。JVM沒法在某通道就緒時直接獲得通知。
    這就是爲何監控多個socket鏈接的傳統的java方案是:爲每一個socket建立一個線程並使線程能夠再read()調用中阻塞,直到數據可用。這實際上將每一個阻塞在對應socket上的線程當作了socket事件監控器,並將JVM的線程調度當作了事件通知。可是線程的阻塞和JVM的線程調用都是爲了這種目的而設計的。當線程數量增加失控時,JVM爲了管理這些線程,致使程序性能下降。」--摘自《JAVA NIO》
上述兩段話摘自《JAVA NIO》第四章-選擇器中,主要闡述了java 傳統NIO的基本實現和弊端。經過上述文字咱們也知道了,提升傳統IO的性能能夠從兩方面入手:1.減小線程數量 2.實時獲取IO事件react


I/O模型

傳統I/O爲了能實時獲取I/O事件,因此纔會給每一個socket鏈接分配一個線程用於監控socket事件,由於如何獲取I/O事件通知是關鍵。所以調整JVM的I/O模型是提升I/O性能關鍵。
咱們來了解下I/O模型的類型:segmentfault

  1. 單線程阻塞I/O模型

    只能同時處理一個客戶端請求,而且在I/O操做上是阻塞的,服務端線程會一直等待I/O操做完成,不會作其餘的事情。服務端讀取客戶端數據時要等待客戶端發送數據而且操做系統內核複製到用戶進程中以後才解除阻塞狀態;服務端寫數據回客戶端是要等待用戶進程將數據寫入內核併發送到客戶端後才解除阻塞狀態。單線程阻塞I/O模型沒法同時處理多個鏈接,只能串行處理鏈接。整個運行過程都只有一個線程,服務端系統資源消耗較小,但併發能力低。安全


  2. 多線程阻塞I/O模型

    利用多線程機制爲每一個客戶端分配一個線程,每一個線程執行讀取客戶端的數據或數據成功寫入客戶端後才解除阻塞狀態。支持多個客戶端併發響應,處理能力獲得提升。系統資源消耗較大,多線程之間會產生線程切換成本,同時爲了實現線程安全引入線程同步機制導成程序結構複雜。網絡


  3. 單線程非阻塞I/O模型

    在調用讀寫接口後當即返回,而不會進入阻塞狀態;基於事件檢測機制獲取到事件發生,進行對應事件的I/O操做。
    事件檢測方式:多線程

    • 應用程序遍歷套接字的事件檢測

      服務端程序會保存一個socket套接字鏈接列表,應用層線程對socket套接字列表輪詢嘗試讀取或寫入。若是嘗試失敗,則在下一個循環再繼續嘗試。查詢每一個socket套接字都至少須要一次系統調用,並且沒法當即獲取socket套接字狀態。同時應用程序除了要遍歷socket套接字列表以外,還須要處理數據的拼接,實際會佔用較多的CPU資源圖片描述併發


    • 內核遍歷套接字的事件檢測

      將遍歷socket列表的工做交給操做系統內核,應用層向發去操做系統內核請求讀寫列表。操做系統內核會遍歷全部套接字並生成對應的可讀列表readList和可寫列表writeList返回給應用程序。應用程序獲取到具體socket,對每一個socket進行相應的I/O操做。圖片描述
      應用程序向內核請求讀寫列表,內核遍歷全部的套接字並生成對應的可讀列表readList和可寫列表writeList。readList標明瞭每一個套接字是否可讀,例如套接字二、套接字3的值爲1表示可讀,套接字1的值爲0表示不可讀;一樣,writeList表示套接字是否可寫。socket


    • 內核基於事件回調的事件檢測

      遍歷套接字列表是個效率比較低的方式,不管是在內核層仍是在應用層。操做系統是可以獲取到I/O事件操做完成的事件,基於回調函數機制和操做系統的I/O操做控制實現事件檢測機制。函數

      1. 基於回調機制的徹底套接字可讀可寫列表

        圖片描述用可讀列表readList和可寫列表writeList標記讀寫事件,套接字的數量與readList和writeList兩個列表的長度同樣。readList元素爲1表示可讀,writeList元素爲1表示可寫。當客戶端發送數據到服務端,內核完成從網卡複製數據調用回調函數將對應套接字readList中的元素置爲1;若套接字已經作好寫操做準備,內核會將套接字對應writeList中的元素置爲1。應用程序發送請求讀、寫事件列表,內核會把包含readList和writeList的事件列表返回給應用程序,應用程序分別遍歷列表,進而記性讀寫操做。性能


      2. 基於回調機制的部分套接字事件列表

        若是套接字數量變大,事件列表傳輸也是不小的開銷。可讓應用層告訴內核每一個套接字感興趣的事件,當客戶端發送數據過來時,內核完成從網卡複製後即調用回調函數將套接字相關信息封裝成可讀事件event放到事件列表中;一樣,內核發現網卡可寫時會將套接字相關信息封裝成可寫事件event添加到事件列表中,事件列表中只有處於ready狀態的那部分套接字對應的事件。圖片描述java NIO便是採用這種事件檢測機制,在客戶端鏈接大多數處於活躍的狀況下,服務端只用一個線程一直循環處理這些鏈接,很好地利用了I/O操做的阻塞時間,提升了線程執行效率。


      3. 多線程非阻塞I/O模型

        單線程非阻塞I/O已經大大提升了機器的執行效率,在多核機器上爲了更好的提升CPU的使用率能夠引入多線程。引入reactor線程模型,每一個線程處理不一樣類型的事件鏈接。


NIO網絡通訊

java NIO性能較java傳統IO有較大提高主要是在網絡交互中,經過Selector來管理客戶端的鏈接,應用程序將客戶端socket鏈接註冊到Selector對象上。當客戶端socket鏈接有數據準備就緒或鏈接準備好寫操做時,應用程序經過selector獲取到對應socket通道(Channel),應用程序經過Channel對socket進行讀寫操做。
NIO中服務端一個線程能夠同時與多個客戶端鏈接進行讀寫交互;而傳統IO則是服務端的一個線程只能同時處理一個客戶端,I/O操做時,線程處於阻塞狀態。
Selector是SelectableChannel的多路複用選擇器,用於監控SelectableChannel的IO情況的。在Selector定義爲SelectableChannel定義了四種事件類型:
SelectionKey.OP_READ        讀事件
SelectionKey.OP_WRITE       寫事件
SelectionKey.OP_CONNECT    接收到客戶端的鏈接事件
SelectionKey.OP_ACCEPT      接收到客戶端的請求事件
多個事件之間用「|」鏈接
當服務端線程註冊事件發生時,操做系統內核會經過回調函數將該事件放到讀寫事件列表中。JVM經過Selector的select方法會獲取到已經註冊到Selector對象上的事件列表。若沒有事件發生select方法會一直阻塞知道有註冊的時間發生纔會把發生的時間返回給Selector對象。

相關文章
相關標籤/搜索