天天看點

netty系列之:netty初探

簡介

netty介紹

netty的第一個伺服器

netty的第一個用戶端

運作伺服器和用戶端

總結

我們常用浏覽器來通路web頁面得到相關的資訊,通常來說使用的都是http或者https協定,這些協定的本質上都是io,用戶端的請求就是in,伺服器的傳回就是out。但是在目前的協定架構中,并不能完全滿足我們所有的需求。比如使用http下載下傳大檔案,可能需要長連接配接等待等。

我們也知道io方式有多種多樣的,包括同步io,異步io,阻塞io和非阻塞io等。不同的io方式其性能也是不同的,而netty就是一個基于異步事件驅動的nio架構。

本系列文章将會探讨netty的詳細使用,通過原理+例子的具體結合,讓大家了解和認識netty的魅力。

netty是一個優秀的nio架構,大家對io的第一映像應該是比較複雜,尤其是跟各種http、tcp、udp協定打交道,使用起來非常複雜。但是netty提供了對這些協定的友好封裝,通過netty可以快速而且簡潔的進行io程式設計。netty易于開發、性能優秀同時兼具穩定性和靈活性。如果你希望開發高性能的服務,那麼使用netty總是沒錯的。

netty的最新版本是4.1.66.final,事實上這個版本是官方推薦的最穩定的版本,netty還有5.x的版本,但是官方并不推薦。

如果要在項目中使用,則可以引入下面的代碼:

下面我們将會從一個最簡單的例子,體驗netty的魅力。

什麼叫做伺服器?能夠對外提供服務的程式就可以被稱為是伺服器。建立伺服器是所有對外服務的第一步,怎麼使用netty建立一個伺服器呢?伺服器主要負責處理各種服務端的請求,netty提供了一個channelinboundhandleradapter的類來處理這類請求,我們隻需要繼承這個類即可。

在nio中每個channel都是用戶端和伺服器端溝通的通道。channelinboundhandleradapter定義了在這個channel上可能出現一些事件和情況,如下圖所示:

netty系列之:netty初探

如上圖所示,channel上可以出現很多事件,比如建立連接配接,關閉連接配接,讀取資料,讀取完成,注冊,取消注冊等。這些方法都是可以被重寫的,我們隻需要建立一個類,繼承channelinboundhandleradapter即可。

這裡我們建立一個firstserverhandler類,并重寫channelread和exceptioncaught兩個方法,第一個方法是從channel中讀取消息,第二個方法是對異常進行處理。

上面例子中,我們收到消息後調用release()方法将其釋放,并不進行實際的處理。調用release方法是在消息使用完成之後常用的做法。上面代碼将msg進行了bytebuf的強制轉換,如果并不想進行轉換的話,可以直接這樣使用:

在異常處理方法中,我們列印出異常資訊,并關閉異常的上下文。

有了handler,我們需要建立一個server類用來使用handler建立channel和接收消息。接下來我們看一下netty的消息處理流程。

在netty中,對io進行處理是使用多線程的event loop來實作的。netty中的eventloopgroup就是這些event loop的抽象類。

我們來觀察一下eventloopgroup的類結構。

netty系列之:netty初探

可以看出eventloopgroup繼承自eventexecutorgroup,而eventexecutorgroup繼承自jdk自帶的scheduledexecutorservice。

是以eventloopgroup本質是是一個線程池服務,之是以叫做group,是因為它裡面包含了很多個eventloop,可以通過調用next方法對eventloop進行周遊。

eventloop是用來處理注冊到該eventloop的channel中的io資訊,一個eventloop就是一個executor,通過不斷的送出任務進行執行。當然,一個eventloop可以注冊多個channel,不過一般情況下并不這樣處理。

eventloopgroup将多個eventloop組成了一個group,通過其中的next方法,可以對group中的eventloop進行周遊。另外eventloopgroup提供了一些register方法,将channel注冊到目前的eventloop中。

從上圖可以看到,register的傳回結果是一個channelfuture,future大家都很清楚,可以用來獲得異步任務的執行結果,同樣的channelfuture也是一個異步的結果承載器,可以通過調用sync方法來阻塞future直到獲得執行結果。

可以看到,register方法還可以傳入一個channelpromise對象,channelpromise它同時是channelfuture和promise的子類,promise又是future的子類,它是一個特殊的可以控制future狀态的future。

eventloopgroup有很多子類的實作,這裡我們使用nioeventloopgroup,nio使用selector對channel進行選擇。還有一個特性是nioeventloopgroup可以添加子eventloopgroup。

對于nio伺服器程式來說,我們需要兩個group,一個group叫做bossgroup,主要用來監控連接配接,一個group叫做worker group,用來處理被boss accept的連接配接,這些連接配接需要被注冊到worker group中才能進行處理。

将這兩個group傳給serverbootstrap,就可以從serverbootstrap啟動服務了,相應的代碼如下:

我們最開始建立的firstserverhandler最作為childhandler的處理器在初始化channel的時候就被添加進去了。

這樣,當有建立立的channel時,firstserverhandler就會被用來處理該channel的資料。

上例中,我們還指定了一些channeloption,用于對channel的一些屬性進行設定。

最後,我們綁定了對應的端口,并啟動伺服器。

上面我們已經寫好了伺服器,并将其啟動,現在還需要一個用戶端和其進行互動。

如果不想寫代碼的話,可以直接telnet localhost 8000和server端進行互動即可,但是這裡我們希望使用netty的api來建構一個client和server進行互動。

建構netty用戶端的流程和建構netty server端的流程基本一緻。首先也需要建立一個handler用來處理具體的消息,同樣,這裡我們也繼承channelinboundhandleradapter。

上一節講到了channelinboundhandleradapter裡面有很多方法,可以根據自己業務的需要進行重寫,這裡我們希望當channel active的時候向server發送一個消息。那麼就需要重寫channelactive方法,同時也希望對異常進行一些處理,是以還需要重寫exceptioncaught方法。如果你想在channel讀取消息的時候進行處理,那麼可以重寫channelread方法。

建立的firstclienthandler代碼如下:

上面的代碼中,我們首先從channelhandlercontext申請了一個bytebuff,然後調用它的writebytes方法,寫入要傳輸的資料。最後調用ctx的writeandflush方法,向伺服器輸出消息。

接下來就是啟動用戶端服務了,在服務端我們建了兩個nioeventloopgroup,是兼顧了channel的選擇和channel中消息的讀取兩部分。對于用戶端來說,并不存在這個問題,這裡隻需要一個nioeventloopgroup即可。

伺服器端使用serverbootstrap來啟動服務,用戶端使用的是bootstrap,其啟動的業務邏輯基本和伺服器啟動一緻:

有了上述的準備工作,我們就可以運作了。首先運作伺服器,再運作用戶端。

如果沒有問題的話,應該會輸出下面的内容:

一個完整的伺服器,用戶端的例子就完成了。我們總結一下netty的工作流程,對于伺服器端,首先建立handler用于對消息的實際處理,然後使用serverbootstrap對eventloop進行分組,并綁定端口啟動。對于用戶端來說,同樣需要建立handler對消息進行處理,然後調用bootstrap對eventloop進行分組,并綁定端口啟動。

有了上面的讨論就可以開發屬于自己的nio服務了。是不是很簡單? 後續文章将會對netty的架構和背後的原理進行深入讨論,敬請期待。

本文的例子可以參考:learn-netty4

最通俗的解讀,最深刻的幹貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!

歡迎關注我的公衆号:「程式那些事」,懂技術,更懂你!