1. 程式人生 > >Java Netty 學習(十)

Java Netty 學習(十)

本篇文章來分析ChannelHandler的相關知識
上一章瞭解到,ChannelHandler起著一個過濾器作用,用於處理在ChannelPipeline中流動的位元組流。
下面慢慢來看。

ChannelInboundHandler

ChannelInboundHandler主要用於處理入站資料以及各種狀態變化,它是ChannelHandler的子類:

public interface ChannelInboundHandler extends ChannelHandler {
   // 提供註冊Channel註冊到EventLoop的事件,提供了ChannelHandler自己的ChannelHandlerContext
    void channelRegistered(ChannelHandlerContext ctx) throws Exception;

  	// Channel取消註冊EventLoop的事件
    void channelUnregistered(ChannelHandlerContext ctx) throws Exception;

    // 判斷Channel是否active
    void channelActive(ChannelHandlerContext ctx) throws Exception;

    // 判斷Channel是否unactive
    void channelInactive(ChannelHandlerContext ctx) throws Exception;

    // 當前的Channel從流中讀取了一條訊息
    void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;

    // 當前的讀操作的最後一個位元組已經被channelRead消費
    void channelReadComplete(ChannelHandlerContext ctx) throws Exception;

    // 當用戶事件被觸發
    void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;

    // 當Channel的寫狀態改變時
    void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;

    // 出錯時
    @Override
    @SuppressWarnings("deprecation")
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}

當然Netty還提供了一個包裝類ChannelInboundHandlerAdapter,繼承這個類,就不用實現ChannelInboundHandler從而實現所有方法了。而ChannelInboundHandlerAdapter給所有方法都提供了預設實現,實質上是呼叫了ChannelHandlerContext的方法。
在這裡插入圖片描述

ChannelOutboundHandler

ChannelOutboundHandler則是處理出站資料並且允許攔截所有操作,當用戶呼叫write方法時,就根據out這根鏈一路傳遞到Channel再流轉到網路上:

public interface ChannelOutboundHandler extends ChannelHandler {
    // 一旦繫結上就呼叫,而當操作完成時候由promise進行提醒
    void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;

    // 當連線上時候,觸發
    void connect(
            ChannelHandlerContext ctx, SocketAddress remoteAddress,
            SocketAddress localAddress, ChannelPromise promise) throws Exception;
    // 斷開連線是觸發
    void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
    // 當關閉操作時被呼叫
    void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
    // 當從當前EventLoop取消註冊時呼叫
    void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
    // 攔截ChannelHandlerContext的read方法
    void read(ChannelHandlerContext ctx) throws Exception;
    // 當一個write操作被呼叫時呼叫
    void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;
    // flush操作,清空快取,並把所有快取資料寫入
    void flush(ChannelHandlerContext ctx) throws Exception;
}

當然,ChannelOutboundHandler也提供了一個包裝類ChannelOutboundHandlerAdapter這裡就不細細分析了。

ByteBuf

Netty中,用ByteBuf作為資料容器,ByteBuf是一個抽象類,裡面包括了char,int,long,boolean,float,double,byte,short 等8種java基本型別的抽象set和get,子類可以根據不同需求去重寫它們。
ByteBuf裡面的操作為陣列操作,裡面維護著兩個索引readerIndexwriteIndex,具體可以參看NIO中Buffer例子:
Java Netty 學習(二) - NIO基礎知識Buffer


所以,由於使用的記憶體是通過JNI方式申請的記憶體空間,這些空間JVM是不會進行處理的,所以當使用完之後,需要注意手動釋放,否則易造成記憶體洩漏。
當使用ChannelInboundHandler.channelReadChannelOutboundHandler.write時候,都需要保證沒有任何的資源洩漏, 所以在完全使用完某個ByteBuf後,調整其引用計數是很重要的。
例如如下程式碼:

@Sharable
public class DiscardInboundHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ReferenceCountUtil.release(msg);
    }
}

write方法

@Sharable
public class DiscardOutboundHandler
    extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx,
        Object msg, ChannelPromise promise) {
        ReferenceCountUtil.release(msg);
        promise.setSuccess();  // 表示訊息已被處理
    }
}

看ReferenceCountUtil的release方法:

    public static boolean release(Object msg) {
        if (msg instanceof ReferenceCounted) {
            return ((ReferenceCounted) msg).release();
        }
        return false;
    }

ReferenceCountedrelease方法,則是將reference count-1,當count=0時,則呼叫deallocates將記憶體物件回收
另外, 可以用Netty提供的ResourceLeakDetector,他將對你的應用程式的緩衝區分配做大約1%的取樣來檢測記憶體洩漏,相關開銷較小。
在每次建立ByteBuf時,建立一個虛引用物件W指向該ByteBuf物件,如果正常呼叫release()操作,則將計數器建議,減1;如果ByteBuf物件不再使用(沒有其他引用)但沒有呼叫release()操作,則GC時虛引用物件A被加入ReferenceQueue中,通過判斷佇列是否為空,即可知道是否存在記憶體洩露。

ChannelHandler流轉

那麼ChannelHandlerChannelPipeline中是如何流轉的呢?
先看下圖,ChannelInboundHanler和ChannelOutboundHandler:
在這裡插入圖片描述
訊息從InboundHandlder一端入站,而從OutboundHandler一端出站。
另一方面, ChannelHandlerChannelHandlerContextChannel之間的關係如下圖所示
在這裡插入圖片描述

在寫法中,呼叫ChannelPipeline的add*方法,將handler加入,同時不同型別的handler執行的事件則不同。

參考: