書接上回,繼續介紹組件。
ChannelHandler組件包含了業務處理核心邏輯,是由用戶自定義的內容,開發人員百分之九十的代碼都是ChannelHandler。Netty提供2個重要的 ChannelHandler 子接口,用來自定義ChannelHandler:git
ChannelInboundHandler - 處理進站數據和全部狀態更改事件(進站指的是讀操做等由通道引起的事件)ChannelOutboundHandler - 處理出站數據,容許攔截各類操做(出站指的是寫操做等由用戶觸發的事件,發送到遠方服務器的事件)編程
來看一下ChannelHandler的類層次結構:segmentfault
上面的類中的Adapter類,提供不少默認操做,好比ChannelHandler中有不少不少方法,咱們用戶自定義的方法有時候不須要重載所有,只須要重載一兩個方法,那麼可使用Adapter類,它裏面有不少默認的方法。其它框架中結尾是Adapter的類的做用也大都是如此。因此咱們在使用netty的時候,每每不多直接實現ChannelHandler的接口,常常是繼承Adapter類。服務器
咱們寫入門例子的時候,重載了幾個方法,這幾個屬於生命週期的一部分。咱們之因此瞭解channel的生命週期,是由於生命週期的每一個階段都會綁定一個事件,咱們能夠捕獲這個事件進行處理,Handler類裏面主要的內容就是處理這些事件。咱們來看一下channel的狀態:網絡
下面是狀態之間的轉換:併發
上面是channel四種類型的狀態和事件,咱們來回顧入門示例中,客戶端在處於active狀態,也就是鏈接到遠程服務器的時候,咱們作了一件事,就是發送一個消息:框架
也就是channel在進行狀態轉換的時候,進站出站的類能夠監聽到,咱們的handler繼承了這些類之後,能夠處理這些事件,重載方法。 tcp
當 ChannelHandler 添加到 ChannelPipeline,或者從ChannelPipeline 移除後,也會有一些狀態的轉換,對應的事件也會被監聽到,對應的方法將會被調用:oop
ChannelInboundHandler主要處理入站事件,入站的時候發生的事件有不少,當接收到數據或者與之關聯的Channel 狀態改變時調用。ChannelInboundHandler的生命週期與 Channel 的生命週期接近,中間有不少和業務相關的事件:this
從這裏咱們就看到了不少熟悉的方法,在咱們的入門例子中,處理過讀事件。在這裏若是相應的事件發生的時候有業務須要進行處理,咱們在寫handler的時候重載相應的方法便可。同理,ChannelOutboundHandler中也有不少方法和事件:
瞭解了ChannelInboundHandler和ChannelOutboundHandler,咱們來看看ChannelPipeline。ChannelPipeline其實就是一個ChannelHandler容器,裏面包括一系列的ChannelHandler 實例,用於攔截流經一個Channel 的入站和出站事件,每一個Channel都有一個ChannelPipeline,咱們要給channel添加處理類,能夠修改 ChannelPipeline 經過動態添加和刪除 ChannelHandler,它定義了豐富的API調用來回應入站和出站事件。
咱們本身定義的handler也都會添加進去:
這裏面能夠調用上面的三個方法操做多個。
有一個問題,創建鏈接後,handler的參數如何傳遞?就是靠ChannelHandlerContext來傳遞的。ChannelHandlerContext表示ChannelHandler 和ChannelPipeline 之間的關聯,在ChannelHandler 添加到 ChannelPipeline 時建立ChannelHandlerContext表示二者之間的關係,
因爲每一個ChannelHandler都對應一個ChannelHandlerContext,因此ChannelHandler之間其實沒有聯繫,都是由ChannelHandlerContext關聯起來的。在咱們的入門例子中,處理讀事件的時候,有印象的必定還記得裏面就是經過ChannelHandlerContext獲取參數的:
介紹完上面的大體能夠總結流程了,服務端有EventLoopGroup,每一個EventLoopGroup有不少EventLoop,每一個EventLoop裏面有Selector,Selector裏面註冊了不少channel,咱們的netty服務端接收的一個個鏈接就是一個個channel,每一個channel都有一個ChannelPipeline,而ChannelPipeline就是ChannelHandler的容器,裏面存放了不少個ChannelHandler實例。ChannelHandler實例就是咱們處理一個個業務的類。咱們一個channel可能有不少ChannelHandler,如何串起來呢?其實多個ChannelHandler直間沒有直接關係,每一個ChannelHandler都對應一個ChannelHandlerContext,ChannelHandlerContext之間是有鏈接關係的,多個ChannelHandler就是靠他們各自對應的ChannelHandlerContext串聯起來的。來看一個流程圖:
ChannelHandlerContext爲何是雙向的呢?其實ChannelPipeline中包含進站和出站操做,都是放在一個鏈表裏面的,進和出的方向確定不一樣,因此ChannelHandlerContext之間是雙向的,好比若是是Inbound操做,那麼在整個鏈表中我只看InBoundHandler,遇到OutBoundHandler就會跳過,因此說,Inbound和OutBound會放在一個鏈中,不是兩條鏈。
咱們再來看一個程序例子,同樣的流程,先看服務端:
這裏面的代碼基本上和入門例子基本同樣,只是channel多個兩個配置,一個是128個阻塞,一個是keepalived。咱們看一下channelpipline的配置:
最下面的ServerHandler仍是咱們的業務處理類,上面的StringDecoder和StringEncoder能夠看出是編碼和解碼的協議,最上面的DelimiterBasedFrameDecoder是一個解碼器,這個解碼器後面的參數是 (8192, Delimiters.lineDelimiter()) ,就是按行解碼,有tcp的數據包來了,好比在消息中有n換行的符號,那就算是一個包,若是到了8192大小的數據尚未,就把包丟棄,好比:
this isn a netty worldn
就是兩個包。
咱們來看ServerHandler裏面的處理,
這個處理也很簡單,channelRead方法裏面就是打印出來,而後回寫回去,末尾加了一個換行符。ctx.channel().remoteAddress()表示遠程鏈接的地址。channelReadComplete方法表示read完後,打印服務端讀取完成,flush就是發送到網絡上。
客戶端的代碼基本上和入門例子同樣:
這裏面的不一樣是在channel的pipline裏面也多加了三個處理器,內容基本上和服務端同樣,客戶端handler代碼:
客戶端的handler中多重載的幾個方法,你們能夠對應一下前面講的生命週期中的狀態,channelRegistered方法中是註冊成功後的打印一條信息,channelActive方法是channel激活並鏈接到遠程後,打印信息併發送一個hello,channelRead方法是客戶端讀取服務端返回的信息後,打印在控制檯上,而後發送給服務端,channelReadComplete方法表示讀取完畢後發送和打印,exceptionCaught方法表示發生異常時的操做,咱們來啓動服務端和客戶端看一下效果:
上面發送的第一個信息hello後面跟了一個n,由於服務端和客戶端都是接到消息後返回給另外一端,致使無限循環互相發送的效果,若是把hello字符串的換行符去掉,看一下效果:
能夠看到沒有打印了,這是由於程序中的第一個handler是根據換行符分隔數據包的,這裏的消息沒有換行符,因此程序認爲消息沒有結束,這時候就會卡在第一個handler裏面,不會走到下面的字符串協議中,更不會走到ServerHandler中。這也是一種網絡編程中的半包問題,也就是包沒有結束,不完整。
pipline中的handler是按照加入的順序處理的,咱們執行的是addLast方法:
這個就決定了幾個handler的順序。
代碼地址:https://gitee.com/blueses/net... 07