1. 程式人生 > >netty的編解碼器介紹

netty的編解碼器介紹

本blog主要介紹:
1. Codec 編解碼器
2. Decoder 解碼器
3. Encoder 編碼器

netty提供了強大的編解碼器框架,使得我們編寫自定義的編解碼器很容易,也容易封裝重用。

在網路應用中需要實現某種編解碼器,將原始位元組資料與自定義的訊息物件進行互相轉換。網路中都是以位元組碼的資料形式來傳輸資料的,伺服器編碼資料後傳送到客戶端,客戶端需要對資料進行解碼。編解碼器由兩部分組成:編碼器、解碼器。

解碼器:負責將訊息從位元組或其他序列形式轉成指定的訊息物件;
編碼器:將訊息物件轉成位元組或其他序列形式在網路上傳輸。

編碼器和解碼器的結構很簡單,訊息被編碼後解碼後會自動通過ReferenceCountUtil.release(message)釋放,如果不想釋放訊息可以使用ReferenceCountUtil.retain(message),這將會使引用數量增加而沒有訊息釋出,大多數時候不需要這麼做。


其實,各種編解碼器的實現都是ChannelHandler的實現。

解碼器

Netty提供了豐富的解碼器抽象基類,我們可以很容易的實現這些基類來自定義解碼器。下面是解碼器的一個型別:

  • 解碼位元組到訊息
  • 解碼訊息到訊息

解碼器負責解碼“入站”資料從一種格式到另一種格式,解碼器處理入站資料是抽象ChannelInboundHandler的實現。實踐中使用解碼器很簡單,就是將入站資料轉換格式後傳遞到ChannelPipeline中的下一個ChannelInboundHandler進行處理;這樣的處理時很靈活的,我們可以將解碼器放在ChannelPipeline中,重用邏輯。

ByteToMessageDecoder

通常我們需要將訊息從位元組解碼成訊息或者從位元組解碼成其他的序列化位元組 。這是一個常見的任務,Netty提供了抽象基類,我們可以使用它們來實現。Netty中提供的ByteToMessageDecoder可以將位元組訊息解碼成POJO物件,下面列出了ByteToMessageDecoder兩個主要方法:

decode(ChannelHandlerContext, ByteBuf, List<Object>)//這個方法是唯一的一個需要自己實現的抽象方法,作用是將ByteBuf資料解碼成其他形式的資料。
decodeLast(ChannelHandlerContext, ByteBuf, List<Object>)//實際上呼叫的是decode(...)。

例如伺服器從某個客戶端接收到一個整數值的位元組碼,伺服器將資料讀入ByteBuf 並經過ChannelPipeline中的每個 ChannelInboundHandle r進行處理,看下圖:
這裡寫圖片描述
上圖顯示了從“入站”ByteBuf讀取bytes後由 ToIntegerDecoder 進行解碼,然後將解碼後的訊息存入List集合中,然後傳遞到ChannelPipeline中的下一個ChannelInboundHandler。看下面ToIntegerDecoder的實現程式碼:

/** 
 * Integer解碼器,ByteToMessageDecoder實現 
 * 
 */  
public class ToIntegerDecoder extends ByteToMessageDecoder {  
    @Override  
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {  
        //接收到位元組大於4個位元組了就處理。
        if(in.readableBytes() >= 4){  
            out.add(in.readInt());  
        }  
    }  
}

從上面的程式碼可能會發現,我們需要檢查ByteBuf讀之前是否有足夠的位元組,這是與TCP黏包有關,若沒有這個檢查豈不更好?是的,Netty提供了這樣的處理允許byte-to-message解碼。除了ByteToMessageDecoder之外,Netty還提供了許多其他的解碼介面。

ReplayingDecoder

ReplayingDecoder是byte-to-message解碼的一種特殊的抽象基類,byte-to-message解碼讀取緩衝區的資料之前需要檢查緩衝區是否有足夠的位元組,使用ReplayingDecoder就無需自己檢查;若ByteBuf中有足夠的位元組,則會正常讀取;若沒有足夠的位元組則會停止解碼。也正因為這樣的包裝使得ReplayingDecoder帶有一定的侷限性。
• 不是所有的操作都被ByteBuf支援,如果呼叫一個不支援的操作會丟擲DecoderException。
• ByteBuf.readableBytes()大部分時間不會返回期望值

如果你能忍受上面列出的限制,相比ByteToMessageDecoder,你可能更喜歡ReplayingDecoder。在滿足需求的情況下推薦使用ByteToMessageDecoder,因為它的處理比較簡單,沒有ReplayingDecoder實現的那麼複雜。ReplayingDecoder繼承於ByteToMessageDecoder,所以他們提供的介面是相同的。下面程式碼是ReplayingDecoder的實現:

/**
 * Integer解碼器,ReplayingDecoder實現
 */
public class ToIntegerReplayingDecoder extends ReplayingDecoder<Void> {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        out.add(in.readInt());
    }
}

3、MessageToMessageDecoder

將訊息物件轉成訊息物件可是使用MessageToMessageDecoder,它是一個抽象類,需要我們自己實現其decode(…)。message-to-message同上面講的byte-to-message的處理機制一樣,看下圖:
這裡寫圖片描述

實現程式碼:

/**
 * 將接收的Integer訊息轉成String型別,MessageToMessageDecoder實現
 * @author c.k
 *
 */
public class IntegerToStringDecoder extends MessageToMessageDecoder<Integer> {

    @Override
    protected void decode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception {
        out.add(String.valueOf(msg));
    }
}

解碼器總結

解碼器是用來處理入站資料,Netty提供了很多解碼器的實現,可以根據需求詳細瞭解。

編碼器

Netty提供了一些基類,我們可以很簡單的編碼器。同樣的,編碼器有下面兩種型別:

  • 訊息物件編碼成訊息物件
  • 訊息物件編碼成位元組碼

相對解碼器,編碼器少了一個byte-to-byte的型別,因為出站資料這樣做沒有意義。編碼器的作用就是將處理好的資料轉成位元組碼以便在網路中傳輸。對照上面列出的兩種編碼器型別,Netty也分別提供了兩個抽象類:MessageToByteEncoder和MessageToMessageEncoder。下面是類關係圖:
類圖

MessageToByteEncoder

MessageToByteEncoder是抽象類,我們自定義一個繼承MessageToByteEncoder的編碼器只需要實現其提供的encode(…)方法。其工作流程如下圖:
這裡寫圖片描述
實現程式碼如下:

/**
 * 編碼器,將Integer值編碼成byte[],MessageToByteEncoder實現
 */
public class IntegerToByteEncoder extends MessageToByteEncoder<Integer> {
    @Override
    protected void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) throws Exception {
        out.writeInt(msg);
    }
}

MessageToMessageEncoder

需要將訊息編碼成其他的訊息時可以使用Netty提供的MessageToMessageEncoder抽象類來實現。例如將Integer編碼成String,其工作流程如下圖:
這裡寫圖片描述

程式碼實現如下:

/**
 * 編碼器,將Integer編碼成String,MessageToMessageEncoder實現
 */
public class IntegerToStringEncoder extends MessageToMessageEncoder<Integer> {

    @Override
    protected void encode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception {
        out.add(String.valueOf(msg));
    }
}