現在優秀的開源項目很是多,僅在 Java 服務器端開發領域,優秀的開源項目就不勝枚舉。好比從十年前就開始流行到如今依舊十分活躍的 Spring Framework,現在已經發展爲一個覆蓋服務器端、大數據等多個領域的平臺級開源項目。還有早年間的 MVC 框架、ORM 框架,到如今涉及各個服務器開發領域的開源技術。java
但此次我要介紹的是 Netty 這個網絡 IO 框架,而非 Spring 這樣的流行項目。緣由在於,Netty 這樣的框架所實現的功能相比 Spring Framework 來講來講更爲基礎。由於對於服務器端開發來講,Spring Framework 核心的 IoC 和 AOP 技術其實並非必須的。固然沒有這兩個技術,開發複雜的項目會很困難,但這二者只是充分而非必要的條件。但 Netty 這樣的技術其實對於服務器端開發來講是必須的。程序員
從產品的角度講,沒有像 Netty 這樣的網絡 IO 框架,就不會有優秀的 Web 服務器、應用服務器等等的平臺,而沒有這些平臺,僅靠 IoC 和 AOP 等技術是不可能產生優秀的軟件產品。一個服務器端軟件,即使沒有直接使用 Netty,那每每也是間接地使用了 Netty 這樣的框架。因此 Netty 這樣的框架對於一個優秀的服務器端項目或產品來講十分重要。編程
同時,由於 Netty 不只被普遍地直接使用,還被不少優秀的技術,例如 Thrift、Storm、Spark 等採用,做爲其基礎的 IO 層的實現技術。因此,深刻了解 Netty 還能夠幫助開發人員更好地使用這些技術,在遇到一些技術問題時也能夠從更深地角度去分析。設計模式
從程序員學習的角度講,像 Netty 這樣的網絡 IO 框架,它們大量使用了 IO 和併發這兩個技術。而這兩個技術,對於優秀的服務器端開發人員來講是必備的。經過深刻了解 Netty 的實現原理,能讓程序員從實踐的角度去學習優秀的 IO 和併發設計。除了 IO 和併發這兩個對於服務器端開發很是重要的技術,瞭解 Netty 的實現原理還可讓程序員去學習如何設計實現一個複雜的框架,學習到設計模式、數據結構等的應用技巧。因此,深刻了解 Netty 對於提升很是有幫助。服務器
從工做的角度講,國內外的不少公司也一樣普遍使用 Netty,好比Twitter、Facebook、華爲、阿里巴巴等等。因此在平常工做中也很容易接觸到 Netty 的使用。網絡
此外,Netty 的文檔相對 Spring 這樣的技術來講仍是偏匱乏。同時,Netty 3/4/5 這幾個版本的變化仍是很是大的,尤爲是從 3 到 4。這給使用者帶來了不少的問題,而深刻了解 Netty 會幫助使用者解決這些問題。數據結構
因此,總結一下,深刻了解 Netty 的好處有:多線程
Java NIO 對於 Netty 來講是基礎技術(Java NIO 是基於 Linux epoll 等技術),因此下面將介紹一下 Java NIO 方面的基礎知識。併發
對於併發技術,由於它相比較 Java NIO 來講在工做中被直接使用到的機會要多得多,因此這裏就不作介紹了。可是後續會介紹 Netty 中的併發設計。框架
Selector selector = null; ServerSocketChannel server = null; try { selector = Selector.open(); // 打開 Selector server = ServerSocketChannel.open(); // 打開 ServerSocketChannel server.socket().bind(new InetSocketAddress(port)); // 綁定端口 server.configureBlocking(false); // 設置爲非阻塞模式 server.register(selector, SelectionKey.OP_ACCEPT); // 將 ServerSocketChannel 註冊到 Selector 上 while (true) { selector.select(); for (Iterator<SelectionKey> i = selector.selectedKeys().iterator(); i.hasNext();) { SelectionKey key = i.next(); i.remove(); if (key.isConnectable()) { ((SocketChannel)key.channel()).finishConnect(); } if (key.isAcceptable()) { // accept connection SocketChannel client = server.accept(); // 接受 TCP 鏈接 client.configureBlocking(false); client.socket().setTcpNoDelay(true); client.register(selector, SelectionKey.OP_READ); // 將 SocketChannel 註冊到 Selector 上 } if (key.isReadable()) { // ...read messages... } } } } catch (Throwable e) { throw new RuntimeException("Server failure: "+e.getMessage()); } finally { try { selector.close(); server.socket().close(); server.close(); stopped(); } catch (Exception e) { // do nothing - server failed } }
上面這段代碼介紹了 Java NIO 的基本使用方式。好比,ServerSocketChannel
和 SocketChannel
要註冊在 Selector
上,而且這兩個 SelectableChannel
要經過 configureBlocking(boolean block)
方法將其設置爲非阻塞模式;經過不斷輪詢 Selector
的方式,經過 Selector.selectedKeys()
得到例如客戶端鏈接、讀寫消息等事件,並作相應處理。由於在一個 Selector
上能夠註冊多個 SelectableChannel
,因此實現了一個線程處理多個鏈接的目的。
上面就是 Java NIO 的大體的使用方法。
Java 這樣編程語言級的 IO 如同線程同樣,最終仍是基於操做系統的實現支持。在 Linux 中,是靠 select、poll 和 epoll 等技術支持的。
在非阻塞 IO 出現以前,咱們是用阻塞的方式使用 IO。當一個流(文件、套接字等)沒法讀寫的時候,操做便被阻塞,線程被掛起。因而,對於一個流的操做,必須獨佔一個線程。雖然能夠用多進程或多線程的方式去併發處理,可是這樣所帶來的大量的資源使用、線程上下文的切換,都使得這種方式十分低效。因此便出現了非阻塞 IO。
最早出現的非阻塞 IO 技術是 select 和 poll。它們的原理簡單來講不斷地輪詢多個流,看是否有流能夠被操做,從而實現了非阻塞的 IO 操做。
但這兩種方式也有明顯的缺點。其缺點在於,select 和 poll 對流的輪詢的方式很「傻」。它倆在輪詢時並不會區分一個流上是否真的有數據,而是一股腦地輪詢全部的流。所以這二者的性能會隨着流的增長而線性降低。
epoll 的意思是 event poll。epoll 相較 select 和 poll 的改進在於不像前者去輪詢全部的流,而是隻去輪詢有實際事件發生的流。這一點的實現是基於硬件中斷實現的。
Channel
表明了 IO 操做的通道。Channel
經過後面提到的 Buffer
進行數據的讀寫。Channel
有一類咱們後面會常常提到的 SelectableChannel
。配合 Selector
,可以實現非阻塞 IO。後續將被常常提到的 ServerSocketChannel
和 SocketChannel
都是 SelectableChannel
的子類。
選擇器,也可被稱爲多路複用器,是實現非阻塞 IO 的關鍵。經過調用 Selector.open()
,即可獲得一個當前操做系統下的默認 Selector
實現。經過 SelectorProvider
修改這一行爲,自定義實現。
SelectableChannel
能夠經過 register(Selector, int)
方法將本身註冊在 Selector
上,並提供其所關注的事件類型。
經過地調用 Selector
的 select()
(阻塞)或 selectNow()
(非阻塞)方法,Selector
會將從上次 select()
方法調用以後的全部就緒狀態通道上的 SelectionKey
放入一個集合中。同時,select()
方法所返回的 int 值表明了有多少 Channel 自上次以後便爲就緒狀態。
在調用 select()
以後,經過調用 Selector
的 selectedKeys()
獲得就緒狀態通道的 SelectionKey
。經過遍歷這一集合,再經過 SelectionKey
即可獲得全部就緒狀態的 SelectableChannel
,進一步即可以作相應的操做。
Java NIO 以 Buffer
最爲數據傳輸的載體。經過使用 Buffer
,Java NIO 提升了數據傳輸的效率。而 Netty 的不少改進也是圍繞 Buffer
進行的,好比 Buffer
的池化。
後面會提到的 SocketChannel
,其使用了 Buffer
的一個子類 ByteBuffer
來進行數據的讀寫。
接下來會介紹 Netty 中一些主要的類,以及它們的做用以及關係。