Netty學習(一)

本章先將Java中IO類型和底層的實現方式。

一、Java IO

Java IO即Java 輸入輸出系統。在Java IO中,流從概念上來說是一個連續的數據流,既可以從流中讀取數據,也可以往流中寫數據。IO相關的媒介包括:

  • 文件
  • 管道
  • 網絡連接
  • 內存緩存
  • System.in, System.out

IO的設計,主要是解決IO相關的操作。從數據傳輸的方式上,分爲字節流和字符流。字節流一次性讀取傳輸一個字節,而字符流則是以字符爲單位進行讀取傳輸。

 

  • 文件類型:FileInputStream,FileOutputStream、FileReader、FileWriter
  • 數組類型:ByteArrayInputStream、ByteArrayOutputStream,CharArrayReader, CharArrayWriter
  • 管道操作:PipedInputStream, PipedOutputStream, Pipedreader, PipedWriter
  • 基本數據類型:DataInputStream、DataOutputStream
  • 緩衝操作:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
  • 打印:PrintStream、PrintWriter
  • 對象序列化反序列化:ObjectInputStream、ObjectOutputStream
  • 轉換:InputStreamReader、OutputStreWriter

 二、什麼是NIO

NIO即new IO,是在JDK1.4引入的,NIO和IO有相同的作用和目的,但實現方式不同,NIO主要用到的是塊,所以NIO的效率要比IO高很多。

NIO的核心對象包括:

  • Buffer:在NIO中,所有的數據都是用Buffer處理的,它是NIO讀寫數據的中轉池。Buffer實質上是一個數組,通常是一個字節數據,但也可以是其他類型的數組。
  • Channel:是一個對象,可以通過channel讀取和寫入數據,是IO中流的抽象。但是channel是雙向的,也可以是異步讀寫,並且channel讀寫必須通過buffer。
  • Selector:是一個對象,可以同時監聽多個channel上發生的事件,並且能夠根據事件情況決定Channel讀寫。
// 打開Selector
Selector selector = Selector.open();
// 將channel註冊到selector
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

Selector感興趣的事件有SelectionKey.OP_CONNECT, SelectionKey.OP_ACCEPT, SelectionKey.OP_READ, SelectionKey.OP_WRITE。

SelectionKey表示通道channel在Selector上的註冊,事件的傳遞是通過SelectionKey,也可以通過selectionKey獲取註冊的channel和對應綁定的selector。

Channel  channel  = selectionKey.channel();
Selector selector = selectionKey.selector(); 

一旦向selector註冊一個或者多個通道後,就可以調用重載的select()方法,select()方法會返回讀事件已經就緒的那些通道

  • int select():阻塞到至少有一個通道的事件就緒
  • int select(long timeout):與select一樣,多個一個超時時間
  • int selectNow():不會阻塞,不管什麼通道就緒都立刻返回,如果沒有通道可選擇,就返回0.

一旦調用了select()方法,它就會返回一個數值,表示一個或多個通道已經就緒,然後你就可以通過調用selector.selectedKeys()方法返回的SelectionKey集合來獲得就緒的Channel。某個線程調用select()方法後阻塞了,即使沒有通道就緒,也有辦法讓其從select()方法返回。

  • 讓其它線程在調用select方法的對象上調用Selector.wakeup()方法即可,阻塞在select()方法上的線程會立馬返回。
  • 如果其它線程調用了wakeup(),但是當前沒有線程阻塞在select上,下一個調用select阻塞的線程會被立即喚醒。

三、BIO, NIO, AIO的區別於聯繫

阻塞和非阻塞

  • 阻塞操作時,當前線程會處於阻塞狀態,無法進行其他任務,只有當滿足一定條件時,才繼續執行;
  • 非阻塞:非阻塞狀態,不會去等待IO操作結束,會立即返回。 線程通常將非阻塞IO的空閒時間用於在其它通道上執行IO操作,所以一個單獨的線程現在可以管理多個輸入和輸出通道(channel)。

BIO:同步並阻塞,服務器實現模式爲一個連接一個線程,即客戶端有連接請求時服務器端就需要啓動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷,當然可以通過線程池機制改善。BIO方式適用於連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,JDK1.4以前的唯一選擇,但程序直觀簡單易理解。

NIO:同步非阻塞,服務器實現模式爲一個請求一個線程,即客戶端發送的連接請求都會註冊到多路複用器上,多路複用器輪詢到連接有I/O請求時才啓動一個線程進行處理。NIO方式適用於連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,併發侷限於應用中,編程比較複雜,JDK1.4開始支持。

AIO:異步非阻塞,服務器實現模式爲一個有效請求一個線程,客戶端的I/O請求都是由OS先完成了再通知服務器應用去啓動線程進行處理.AIO方式使用於連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用OS參與併發操作,編程比較複雜,JDK7開始支持。

四、Java NIO和Netty

直接使用Java NIO的缺點:

  • NIO的類庫和API繁雜,需要熟練掌握Selector,ServerSocketChannel、SocketChannel、ByteBuffer等才能很好使用。
  • 可靠性較弱,需要自行維護,工作量大,例如客戶端面臨斷連重連、網絡閃斷、半包讀寫、失敗緩存、網絡擁塞和異常碼流的處理等問題;
  • JDK NIO的BUG,例如epoll bug,它會導致Selector空輪詢,最終導致CPU 100%。

Netty是對Java NIO的封裝框架,簡化了NIO的使用難度,Netty特性總結如下:

  • API使用簡單,開發門檻低
  • 功能強大,預置了多種編解碼功能,支持多種主流協議
  • 定製能力強,可以通過ChannelHandler對通信框架進行靈活地擴展
  • 性能高,通過與其他業界主流的NIO框架對比,Netty的綜合性能最優
  • 成熟、穩定,Netty修復了已經發現的所有JDK NIO BUG,業務開發人員不需要再爲NIO的BUG而煩惱
  • 社區活躍,版本迭代週期短,發現的BUG可以被及時修復,同時更多的新功能會加入
  • 經歷了大規模的商業應用考驗,質量得到驗證。

五、select、poll、epoll之間的區別

select具有O(n)的無差別輪詢複雜度,同時處理的流越多,無差別輪詢時間就越長。

poll本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,然後查詢每個fd對應的設備狀態, 但是它沒有最大連接數的限制,原因是它是基於鏈表來存儲的。

epoll可以理解爲event poll,是事件驅動(每個事件關聯上fd)的。

select:

select本質上是通過設置或者檢查存放fd標誌位的數據結構來進行下一步處理。這樣所帶來的缺點是:

1、 單個進程可監視的fd數量被限制,即能監聽端口的大小有限。

      一般來說這個數目和系統內存關係很大,具體數目可以cat /proc/sys/fs/file-max察看。32位機默認是1024個。64位機默認是2048.

2、 對socket進行掃描時是線性掃描,即採用輪詢的方法,效率較低:

       當套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。如果能給套接字註冊某個回調函數,當他們活躍時,自動完成相關操作,那就避免了輪詢,這正是epoll與kqueue做的。

3、需要維護一個用來存放大量fd的數據結構,這樣會使得用戶空間和內核空間在傳遞該結構時複製開銷大

poll:

poll本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,然後查詢每個fd對應的設備狀態,如果設備就緒則在設備等待隊列中加入一項並繼續遍歷,如果遍歷完所有fd後沒有發現就緒設備,則掛起當前進程,直到設備就緒或者主動超時,被喚醒後它又要再次遍歷fd。這個過程經歷了多次無謂的遍歷。

它沒有最大連接數的限制,原因是它是基於鏈表來存儲的,但是同樣有一個缺點:

1、大量的fd的數組被整體複製於用戶態和內核地址空間之間,而不管這樣的複製是不是有意義。                   

2、poll還有一個特點是“水平觸發”,如果報告了fd後,沒有被處理,那麼下次poll時會再次報告該fd。

epoll有EPOLLLT和EPOLLET兩種觸發模式,LT是默認的模式,ET是“高速”模式。epoll的優點:

1、沒有最大併發連接的限制,能打開的FD的上限遠大於1024(1G的內存上能監聽約10萬個端口)
2、效率提升,不是輪詢的方式,不會隨着FD數目的增加效率下降。只有活躍可用的FD纔會調用callback函數;

3、用MMP加速內核與用戶空間的消息傳遞。


即Epoll最大的優點就在於它只管你“活躍”的連接,而跟連接總數無關,因此在實際的網絡環境中,Epoll的效率就會遠遠高於select和poll。

EPOLLLT模式下,系統中一旦有大量不需要讀寫的就緒文件描述符,它們每次調用epoll_wait都會返回,這樣會大大降低處理程序檢索自己關心的就緒文件描述符的效率.。

採用EPOLLET這種邊沿觸發模式的話,當被監控的文件描述符上有可讀寫事件發生時,epoll_wait()會通知處理程序去讀寫。如果這次沒有把數據全部讀寫完(如讀寫緩衝區太小),那麼下次調用epoll_wait()時,它不會通知,直到該文件描述符上出現第二次可讀寫事件纔會通知你!!!這種模式比水平觸發效率高,系統不會充斥大量你不關心的就緒文件描述符

發佈了150 篇原創文章 · 獲贊 25 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章