本小節一起學習一下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的啟動類代碼:
- 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();
- }
我們在channelhandler鏈中加了兩個自定義的BaseClient1Handler和BaseClient2Handler的處理器
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之間的關系就是如上總結的
以上三點是我自己總結的,沒看源碼,有些也可能不對,歡迎拍磚,一起學習的過程,不保證全部對~