netty中的ChannelHandler

本文主要介紹如下幾個相關類: 
• ChannelPipeline 
• ChannelHandlerContext 
• ChannelHandler 
• Inbound vs outbound(入站和出站)java

接受客戶端的鏈接和建立鏈接只是應用程序中的一步,更加劇要的仍是處理傳入傳出的數據。netty提供了強大的事件處理機制,容許用戶自定義ChannelHandler的實現來處理數據。安全

1.ChannelPipeline

ChannelPipeline是ChannelHandler實例的列表(或則說是容器),用於處理或截獲通道的接收和發送數據。ChannelPipeline提供了一種高級的截取過濾器模式,讓用戶能夠在ChannelPipeline中徹底控制一個事件及如何處理ChannelHandler與ChannelPipeline的交互。ide

能夠這樣說,一個新的通道就對應一個新的ChannelPipeline並附加至通道。一旦鏈接,通道Channel和ChannelPipeline之間的耦合是永久性的。通道Channel不能附加其餘的ChannelPipeline或從ChannelPipeline分離。oop

下圖顯示了ChannelHandler在ChannelPipeline中的IO處理示意圖: 
這裏寫圖片描述this

很明顯,ChannelPipeline裏面就是一個ChannelHandler的列表。若是一個入站IO事件被觸發,這個事件會從第一個開始依次經過ChannelPipeline中的ChannelHandler。如果一個入站I/O事件,則會從最後一個開始依次經過ChannelPipeline中的ChannelHandler。ChannelHandler能夠處理事件並檢查類型,若是某個ChannelHandler不能處理則會跳過,並將事件傳遞到下一個ChannelHandler。ChannelPipeline能夠動態添加、刪除、替換其中的ChannelHandler,這樣的機制能夠提升靈活性。spa

修改ChannelPipeline的方法: 
• addFirst(…),添加ChannelHandler在ChannelPipeline的第一個位置 
• addBefore(…),在ChannelPipeline中指定的ChannelHandler名稱以前添加ChannelHandler 
• addAfter(…),在ChannelPipeline中指定的ChannelHandler名稱以後添加ChannelHandler 
• addLast(ChannelHandler…),在ChannelPipeline的末尾添加ChannelHandler 
• remove(…),刪除ChannelPipeline中指定的ChannelHandler 
• replace(…),替換ChannelPipeline中指定的ChannelHandler線程

好比:netty

ChannelPipeline pipeline = ch.pipeline();  
FirstHandler firstHandler = new FirstHandler();  
pipeline.addLast("handler1", firstHandler);  
pipeline.addFirst("handler2", new SecondHandler());  
pipeline.addLast("handler3", new ThirdHandler());  
pipeline.remove("「handler3「");  
pipeline.remove(firstHandler);  
pipeline.replace("handler2", "handler4", new FourthHandler());

被添加到ChannelPipeline的ChannelHandler將經過IO-Thread處理事件,這意味了必須不能有其餘的IO-Thread阻塞來影響IO的總體處理;有時候可能須要阻塞,例如JDBC。所以netty容許經過一個EventExecutorGroup到每個ChannelPipeline.add*方法,自定義的事件會被包含在EventExecutorGroup中的EventExecutor來處理,默認的實現是DefaultEventExecutorGroup。code

2.ChannelHandlerContext

每一個ChannelHandler被添加到ChannelPipeline後,都會建立一個ChannelHandlerContext並與之建立的ChannelHandler關聯綁定。ChannelHandlerContext容許ChannelHandler與其餘的ChannelHandler實現進行交互。ChannelHandlerContext不會改變添加到其中的ChannelHandler,所以它是安全的。對象

下圖顯示了ChannelHandlerContext、ChannelHandler、ChannelPipeline的關係: 
這裏寫圖片描述

ChannelHandlerContext能夠通知下一個ChannelHandler

若是咱們想有一些事件流所有經過ChannelPipeline,有兩個不一樣的方法能夠作到: 
• 調用Channel的方法 
• 調用ChannelPipeline的方法

這兩個方法均可以讓事件流所有經過ChannelPipeline。不管從頭部仍是尾部開始,由於它主要依賴於事件的性質。若是是一個「入站」事件,它開始於頭部;如果一個「出站」事件,則開始於尾部。

下面的代碼顯示了一個寫事件如何經過ChannelPipeline從尾部開始:

protected void initChannel(SocketChannel ch) throws Exception {  
    ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {  
        @Override  
        public void channelActive(ChannelHandlerContext ctx) throws Exception {  
            //Event via Channel  
            Channel channel = ctx.channel();  
            channel.write(Unpooled.copiedBuffer("netty in action", CharsetUtil.UTF_8));  

            //Event via ChannelPipeline  
            ChannelPipeline pipeline = ctx.pipeline();  
            pipeline.write(Unpooled.copiedBuffer("netty in action", CharsetUtil.UTF_8));  
        }  
    });  
}

下圖表示經過Channel或ChannelPipeline的通知: 
這裏寫圖片描述

ChannelHandlerContext能夠修改ChannelPipeline

調用ChannelHandlerContext的pipeline()方法能訪問ChannelPipeline,能在運行時動態的增長、刪除、替換ChannelPipeline中的ChannelHandler。咱們能夠保持ChannelHandlerContext以供之後使用,如外部Handler方法觸發一個事件,甚至從一個不一樣的線程觸發。 
下面代碼顯示了保存ChannelHandlerContext供以後使用或其餘線程使用:

public class WriteHandler extends ChannelHandlerAdapter {
    private ChannelHandlerContext ctx;

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        this.ctx = ctx;
    }

    public void send(String msg){
        ctx.write(msg);
    }
}
  •  

請注意,ChannelHandler實例若是帶有@Sharable註解則能夠被添加到多個ChannelPipeline。也就是說單個ChannelHandler實例能夠有多個ChannelHandlerContext,所以能夠調用不一樣ChannelHandlerContext獲取同一個ChannelHandler。若是添加不帶@Sharable註解的ChannelHandler實例到多個ChannelPipeline則會拋出異常;使用@Sharable註解後的ChannelHandler必須在不一樣的線程和不一樣的通道上安全使用。怎麼是不安全的使用?看下面代碼:

@Sharable
public class NotSharableHandler extends ChannelInboundHandlerAdapter {

    private int count;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        count++;
        System.out.println("channelRead(...) called the " + count + " time「");
        ctx.fireChannelRead(msg);
    }

}
  •  

上面是一個帶@Sharable註解的Handler,它被多個線程使用時,裏面count是不安全的,會致使count值錯誤。 
爲何要共享ChannelHandler?使用@Sharable註解共享一個ChannelHandler在一些需求中仍是有很好的做用的,如使用一個ChannelHandler來統計鏈接數或來處理一些全局數據等等。

3. channel的狀態模型

Netty有一個簡單但強大的狀態模型,並完美映射到ChannelInboundHandler的各個方法。下面是Channel生命週期四個不一樣的狀態: 
1. channelUnregistered 
2. channelRegistered 
3. channelActive 
4. channelInactive

Channel的狀態在其生命週期中變化,由於狀態變化須要觸發,下圖顯示了Channel狀態變化: 
這裏寫圖片描述

四、ChannelHandler和其子類

先看一些類的繼承圖: 
這裏寫圖片描述

一、ChannelHandler中的方法

Netty定義了良好的類型層次結構來表示不一樣的處理程序類型,全部的類型的父類是ChannelHandler。ChannelHandler提供了在其生命週期內添加或從ChannelPipeline中刪除的方法。

1. handlerAdded,ChannelHandler添加到實際上下文中準備處理事件 
2. handlerRemoved,將ChannelHandler從實際上下文中刪除,再也不處理事件 
3. exceptionCaught,處理拋出的異常

netty還提供了一個實現了ChannelHandler的抽象類ChannelHandlerAdapter。ChannelHandlerAdapter實現了父類的全部方法,基本上就是傳遞事件到ChannelPipeline中的下一個ChannelHandler直到結束。咱們也能夠直接繼承於ChannelHandlerAdapter,而後重寫裏面的方法。

二、ChannelInboundHandler

ChannelInboundHandler提供了一些方法再接收數據或Channel狀態改變時被調用。下面是ChannelInboundHandler的一些方法: 
1. channelRegistered,ChannelHandlerContext的Channel被註冊到EventLoop; 
2. channelUnregistered,ChannelHandlerContext的Channel從EventLoop中註銷 
3. channelActive,ChannelHandlerContext的Channel已激活 
4. channelInactive,ChannelHanderContxt的Channel結束生命週期 
5. channelRead,從當前Channel的對端讀取消息 
6. channelReadComplete,消息讀取完成後執行 
7. userEventTriggered,一個用戶事件被處罰 
8. channelWritabilityChanged,改變通道的可寫狀態,可使用Channel.isWritable()檢查 
9. exceptionCaught,重寫父類ChannelHandler的方法,處理異常

netty提供了一個實現了ChannelInboundHandler接口並繼承ChannelHandlerAdapter的類:ChannelInboundHandlerAdapter。ChannelInboundHandlerAdapter實現了ChannelInboundHandler的全部方法,做用就是處理消息並將消息轉發到ChannelPipeline中的下一個ChannelHandler。ChannelInboundHandlerAdapter的channelRead方法處理完消息後不會自動釋放消息,若想自動釋放收到的消息,可使用SimpleChannelInboundHandler

看下面的代碼:

/**
 * 實現ChannelInboundHandlerAdapter的Handler,不會自動釋放接收的消息對象
 * @author c.k
 *
 */
public class DiscardHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //手動釋放消息
        ReferenceCountUtil.release(msg);
    }
}
  •  

SimpleChannelInboundHandler會自動釋放消息

/**
 * 繼承SimpleChannelInboundHandler,會自動釋放消息對象
 * @author c.k
 *
 */
public class SimpleDiscardHandler extends SimpleChannelInboundHandler<Object> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        //不須要手動釋放
    }
}
  •  

ChannelInitializer用來初始化ChannelHandler,將自定義的各類ChannelHandler添加到ChannelPipeline中。

相關文章
相關標籤/搜索