一起學Netty(四)之 ChannelHandler,ChannelHandlerContext,ChannelPipeline
本小節一起學習一下ChannelHandler,ChannelHandlerContext,ChannelPipeline這三個Netty常用的元件,不探究它們的底層原始碼,我們就簡單的分析一下用法
首先先分析一下ChannelHandler,ChannelHandler是我們日常開發中使用最多的元件了,大概我們平時寫的最多的元件就是Handler了,繼承圖如下
我們平時繼承的最多的就是ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter,這兩個不是介面也不是抽象類,所以我們可以僅僅重寫我們需要的方法,沒有必須要實現的方法,當然我們也會使用SimpleChannelInboundHandler,這個類我們上個小節也稍微講了它的優缺點,這裡不贅述
ChannelHandler,ChannelHandlerContext,ChannelPipeline這三者的關係很特別,相輔相成,一個ChannelPipeline中可以有多個ChannelHandler例項,而每一個ChannelHandler例項與ChannelPipeline之間的橋樑就是ChannelHandlerContext例項,如圖所示:
看圖就知道,ChannelHandlerContext的重要性了,如果你獲取到了ChannelHandlerContext的例項的話,你可以獲取到你想要的一切,你可以根據ChannelHandlerContext執行ChannelHandler中的方法,我們舉個例子來說,我們可以看下ChannelHandlerContext部分API:
這幾個API都是使用比較頻繁的,都是呼叫當前handler之後同一型別的channel中的某個方法,這裡的同一型別指的是同一個方向,比如inbound呼叫inbound,outbound呼叫outbound型別的channel,一般來說,都是一個channel的ChannnelActive方法中呼叫fireChannelActive來觸發呼叫下一個handler中的ChannelActive方法
我們舉例來說,我們修改Helloworld版中的部分程式碼,在客戶端中的channel中修改一下程式碼
首先我們修改一下bootstrap的啟動類程式碼:
我們在channelhandler鏈中加了兩個自定義的BaseClient1Handler和BaseClient2Handler的處理器try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast("decoder", new StringDecoder()); p.addLast("encoder", new StringEncoder()); p.addLast(new BaseClient1Handler()); p.addLast(new BaseClient2Handler()); } }); ChannelFuture future = b.connect(HOST, PORT).sync(); future.channel().writeAndFlush("Hello Netty Server ,I am a common client"); future.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); }
BaseClient1Handler的方法也很簡單:
BaseClient1Handler.java
package com.lyncc.netty.component.channelhandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
*
* @author bazingaLyncc
* 描述:客戶端的第一個自定義的inbound處理器
* 時間 2016年5月3日
*/
public class BaseClient1Handler extends ChannelInboundHandlerAdapter{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("BaseClient1Handler channelActive");
// ctx.fireChannelActive();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("BaseClient1Handler channelInactive");
}
}
BaseClient2Handler.java
package com.lyncc.netty.component.channelhandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
*
* @author bazingaLyncc
* 描述:客戶端的第二個自定義的inbound處理器
* 時間 2016年5月3日
*/
public class BaseClient2Handler extends ChannelInboundHandlerAdapter{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("BaseClient2Handler Active");
}
}
其他的程式碼不修改的,我們先啟動伺服器端,然後啟動客戶端,你會發現控制檯列印了:
不會列印BaseClient2Handler類中channelActive方法中的輸出語句,如果你想要列印,你可以將BaseClient1Handler中的channelActive的ctx.fireChannelActive()註釋去掉。重新執行:
也就是說如果一個channelPipeline中有多個channelHandler時,且這些channelHandler中有同樣的方法時,例如這裡的channelActive方法,只會呼叫處在第一個的channelHandler中的channelActive方法,如果你想要呼叫後續的channelHandler的同名的方法就需要呼叫以“fire”為開頭的方法了,這樣做很靈活
目前來說這樣做的好處:
1)每一個handler只需要關注自己要處理的方法,如果你不關注channelActive方法時,你自定義的channelhandler就不需要重寫channelActive方法
2)異常處理,如果 exceptionCaught方法每個handler都重寫了,只需有一個類捕捉到然後做處理就可以了,不需要每個handler都處理一遍
3)靈活性。例如如下圖所示:
如圖所示在業務邏輯處理中,也許左側第一個ChannelHandler根本不需要管理某個業務邏輯,但是從第二個ChannelHandler就需要關注處理某個業務需求了,那麼就可以很靈活地從第二個ChannelHandler開始處理業務,不需要從channel中的第一個ChannelHandler開始處理,這樣會使程式碼顯得讓人看不懂~
初步看懂的ChannelHandler,ChannelHandlerContext,ChannelPipeline之間的關係就是如上總結的
以上三點是我自己總結的,沒看原始碼,有些也可能不對,歡迎拍磚,一起學習的過程,不保證全部對~