當前位置:
首頁 > 最新 > Netty實戰四之傳輸

Netty實戰四之傳輸

流經網路的數據總是具有相同的類型:位元組(網路傳輸——一個幫助我們抽象底層數據傳輸機制的概念)

Netty為它所有的傳輸實現提供了一個通用的API,即我們可以將時間花在其他更有成效的事情上。

我們將通過一個案例來對傳輸進行學習,應用程序只簡單地接收連接,向客戶端寫 「Hi!」 ,然後關閉連接。

1、不通過Netty使用OIO和NIO

先介紹JDK API的應用程序的阻塞(OIO)版本和非同步(NIO)版本。

這段代碼完全可以處理中等數量的並發客戶端,但是隨著應用程序變得流行起來,你會發現它並不能很好地伸縮到支撐成千上萬的並發連入連接。你決定改用非同步網路編程,但是很快就發現非同步API是完全不同的,以至於現在你不得不重寫你的應用程序。

雖然這段代碼所做的事情與之前的版本完全相同,但是代碼卻截然不同,如果為了用於非阻塞I/O而重新實現這個簡單的應用程序,都需要一次完全重寫的話,那麼不難想像,移植真正複雜的應用程序需要付出什麼樣的努力!

2、通過Netty使用OIO和NIO

3、非阻塞的Netty版本

因為Netty為每種傳輸的實現都暴露了相同的API,所以無論選用哪一種傳輸的實現,你的代碼都仍然幾乎不受影響,在所有的情況下,傳輸的實現都依賴於interface Channel、ChannelPipeline和ChannelHandler。

4、傳輸API

傳輸API的核心是interface Channel ,它被用於所有的I/O操作。Channel類的層次結構如圖4-1(Channel介面的層次結構)所示。

如圖所示,每個Channel都將會將分配一個ChannelPipeline和ChannelConfig。ChannelConfig包含了該Channel的所有配置設置,並且支持熱更新。

由於特定的傳輸可能具有獨特的設置,所以它可能會實現一個ChannelConfig的子類型。

由於Channel是獨一無二的,所以為了保證順序將Channel聲明為java.lang.Comparable的子介面。因此,如果兩個不同的Channel實例都返回相同的散列碼,那麼AbstractChannel中的compareTo()方法的實現將會拋出一個Error。

ChannelPiepeline持有所有將應用於入站和出站數據以及事件的ChannelHandler實例,這些ChannelHandler實現了應用程序用於處理狀態變化以及數據處理的邏輯。

ChannelHandler的典型用途包括:

-將數據從一種格式轉換為另一種格式

-提供異常的通知

-提供Channel變為活動的或者非活動的通知

-提供當Channel註冊到EventLoop或者從EventLoop註銷時的通知

-提供有關用戶自定義事件的通知

攔截過濾器 ChannelPipeline實現了一種常見的設計模式——攔截過濾器(InterceptingFilter)。UNIX管道是另外一個熟悉的例子:多個命令被鏈接在一起,其中一個命令的輸出端將連接到命令行中下一個命令的輸入端。

你也可以根據需要通過添加或者移除ChannelHandler實例來修改ChannelPipeline。通過利用Netty的這項能力可以構建出高度靈活的應用程序。例如,每當STARTTLS協議被請求時,你可以簡單地通過向ChannelPipeline添加一個適當的ChannelHandler(SslHandler)來按需地支持STARTTLS協議。

考慮一下寫數據並將其沖刷到遠程節點這樣的常規任務,代碼清單4-5演示了使用Channel.writeAndFlush()來實現這一目的。

Netty的Channel實現是線程安全的,因此你可以存儲一個到Channel的引用,並且每當你需要向遠程節點寫數據時,都可以使用它,即使當時許多線程都在使用它。代碼清單4-6展示了一個多線程寫數據的簡單例子,需要注意的是,消息將會被保證按順序發送的。

5、內置的傳輸

Netty內置了一些可開箱即用的傳輸,因為並不是它們所有的傳輸都支持每一種協議,所以你必須選擇一個和你的應用程序所使用的協議相容的傳輸。 下表顯示了所有Netty提供的傳輸

6、NIO——非阻塞I/O

NIO提供了一個所有I/O操作的全非同步的實現。它利用了自NIO子系統被引入JDK1.4時便可用的基於選擇器的API。

選擇器背後的基本概念是充當一個註冊表,在那裡你將可以請求在Channel的狀態發生變化時得到通知。

-新的Channel已被接受並且就緒

-Channel連接已經完成

-Channel有已經就緒的可供讀取的數據

-Channel可用於寫數據

選擇器運行在一個檢查狀態變化並對其做出相應響應的線程上,在應用程序對狀態的改變做出響應之後,選擇器將會被重置,並將重複這個過程。

零拷貝(zero-copy)是一種目前只有在使用NIO和Epoll傳輸時才可使用的特性。它使你可以快速高效地將數據從文件系統移動到網路介面,而不需要將其從內核空間複製到用戶空間,其在像FTP或者HTTP這樣的協議中可以顯著地提升性能。但是,並不是所有的操作系統都支持這一特性。特別地,它對於實現了數據加密或者壓縮的文件系統是不可用的——只能傳輸文件的原始內容。反過來說,傳輸已被加密的文件則不是問題。

7、Epoll——用於Linux的本地非阻塞傳輸

Linux作為高性能網路編程的平台,其重要性與日俱增,這催生了大量先進特性的開發,其中包括Epoll——一個高度可擴展的I/O事件通知特性,這個API自Linux內核版本2.5.44被引入,提供了比舊的POSIX select和poll系統調用更好的性能,同時現在也是Linux上非阻塞網路編程的事實標準。Linux JDK NIO API使用了這些epoll調用。

Netty為Linux提供了一組NIO API,其以一種和它本身的設計更加一致的方式使用epoll,並且以一種更加輕量的方式使用中斷,如果你的應用程序旨在運行於Linux系統,那麼請考慮利用這個版本的傳輸,你將發現在高負載下它的性能要優於JDK的NIO實現。

8、OIO——舊的阻塞I/O

Netty的OIO傳輸實現代表了一種折中:它可以通過常規的傳輸API使用,但是由於它是建立在java.net包的阻塞實現上的,所以他不是非同步的。

例如,你可能需要移植使用了一些進行阻塞調用的庫(如JDBC)的遺留代碼,而將邏輯轉換為非阻塞的可能也是不切實際。相反,你可以在短期內使用Netty的OIO傳輸,然後再將你的代碼移植到純粹的非同步傳輸上。

在 java.net API中,你通常會有一個用來接受到達正在監聽的ServerSocket的新連接的線程。會創建一個新的和遠程節點進行交互的套接字,並且會分配一個新的用於處理響應通信流量的線程。這是必需的,因為某個指定套接字上的任何I/O操作在任意的時間點上都可能會阻塞。使用單個線程來處理多個套接字,很容易導致一個套接字上的阻塞操作也捆綁了所有其他的套接字。

Netty是如何能夠使用和用於非同步傳輸相同的API支持OIO的呢?Netty利用了SO_TIMEOUT這個Socket標誌,它指定了等待一個I/O操作完成的最大毫秒數。如果操作在指定的時間間隔內沒有完成,則將會拋出一個SocketTimeout Exception。Netty將捕獲這個異常並繼續處理循環。在EventLoop下一次運行時,它將再次嘗試,這也是Netty這樣的非同步框架能夠支持OIO的唯一方式。

9、用於JVM內部通信的Local傳輸

Netty提供了一個Local傳輸,用於在同一個JVM中運行的客戶端和伺服器程序之間的非同步通信,且也支持對於所有Netty傳輸實現都共同的API。

在這個傳輸中,和伺服器Channel相關聯的SocketAddress並沒有綁定物理網路地址;相反,只要伺服器還在運行,它就會被存儲在註冊表裡,並在Channel關閉時註銷。因為這個傳輸並不接受真正的網路流量,所以它並不能夠和其他傳輸實現進行互操作。因此,客戶端希望連接到(在同一個JVM中)使用了這個傳輸的伺服器端時也必須使用它。除了這個限制,它的使用方式和其他傳輸一模一樣。

10、Embedded傳輸

Netty提供了一種額外的傳輸,使得你可以將一組ChannelHandler作為幫助器類嵌入到其他的ChannelHandler內部。通過這種方式,你將可以擴展一個CHannelHandler的功能,而又不需要修改其內部代碼。

11、傳輸的用例

在Linux上啟用SCTP

SCTP需要內核的支持,並且需要安裝用戶庫

例如,對於Ubuntn,可以使用下面的命令


sudo apt-get install libsctpl

對於Fedora,可以使用yum


sudo yum install kernel-modules-extra.x86_64 lksctp-tools.x86_64

有關如何啟用SCTP的詳細信息,請參考你的Linux發行版的文檔。

雖然只有SCTP傳輸有這些特殊要求,但是其他傳輸可能也有它們自己的配置選項需要考慮。此外,如果只是為了支持更高的並發連接數,伺服器平台可能需要配置得和客戶端不一樣。

——非阻塞代碼庫:如果你的代碼庫中沒有阻塞調用(或者你能夠限制它們的範圍),那麼在Linux上使用NIO或者epoll始終是個好主意。雖然NIO/Epoll旨在處理大量的並發連接,但是在處理較小數目的並發連接時,它也能很好地工作,尤其是考慮到它在連接之間共享線程的方式。

——阻塞代碼庫:如果你的代碼庫嚴重地依賴於阻塞I/O,而且你的應用程序也有一個相應的設計,那麼在你嘗試將其直接轉換為Netty的NIO傳輸時,你將可能會遇到和阻塞操作相關的問題。不要為此而重寫你的代碼,可以考慮分階段遷移:先從OIO開始,等你的代碼修改好之後,在遷移到NIO(或者EPoll,如果你在使用Linux)

——在同一個JVM內部的通信:同一個JVM內部的通信,不需要通過網路暴露服務,是Local傳輸的完美用例。這將消除所有真實網路操作的開銷,同時仍然使用你的Netty代碼庫。如果隨後需要通過網路暴露服務,那麼你將只需要把傳輸改為NIO或者OIO即可。

——測試你的ChannelHandler實現:如果你想要為自己的ChannelHandler實現編寫單元測試,那麼請考慮使用Embedded傳輸。這既便於測試你的代碼,而又不需要創建大量的模擬對象。你的類將仍然符合常規API事件流,保證該Channelhandler在和真實的傳輸一起使用時能夠正確地工作。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Java貓說 的精彩文章:

TAG:Java貓說 |