Netty提供的Decoder(解碼器)

2018-08-08 10:39 更新

在本節(jié)內(nèi)容中,我們會提供幾個類用于 decoder 的實現(xiàn),并且通過一些具體的例子來告訴大家什么情況下可以使用它們以及它們的使用方法。

Netty 提供了豐富的解碼器抽象基類,我們可以很容易的實現(xiàn)這些基類來自定義解碼器。主要分兩類:

  • 解碼字節(jié)到消息(ByteToMessageDecoder 和 ReplayingDecoder)
  • 解碼消息到消息(MessageToMessageDecoder)

decoder 負責(zé)將“入站”數(shù)據(jù)從一種格式轉(zhuǎn)換到另一種格式,Netty的解碼器是一種 ChannelInboundHandler 的抽象實現(xiàn)。實踐中使用解碼器很簡單,就是將入站數(shù)據(jù)轉(zhuǎn)換格式后傳遞到 ChannelPipeline 中的下一個ChannelInboundHandler 進行處理;這樣的處理是很靈活的,我們可以將解碼器放在 ChannelPipeline 中,重用邏輯。

ByteToMessageDecoder

ByteToMessageDecoder 是用于將字節(jié)轉(zhuǎn)為消息(或其他字節(jié)序列)。

你不能確定遠端是否會一次發(fā)送完一個完整的“信息”,因此這個類會緩存入站的數(shù)據(jù),直到準備好了用于處理。表7.1說明了它的兩個最重要的方法。

Table 7.1 ByteToMessageDecoder API

方法名稱描述
DecodeThis is the only abstract method you need to implement. It is called with a ByteBuf having the incoming bytes and a List into which decoded messages are added. decode() is called repeatedly until the List is empty on return. The contents of the List are then passed to the next handler in the pipeline.
decodeLastThe default implementation provided simply calls decode().This method is called once, when the Channel goes inactive. Override to provide special

handling

假設(shè)我們接收一個包含簡單整數(shù)的字節(jié)流,每個都單獨處理。在本例中,我們將從入站 ByteBuf 讀取每個整數(shù)并將其傳遞給 pipeline 中的下一個ChannelInboundHandler?!敖獯a”字節(jié)流成整數(shù)我們將擴展ByteToMessageDecoder,實現(xiàn)類為“ToIntegerDecoder”,如圖7.1所示。

Figure%207

Figure 7.1 ToIntegerDecoder

每次從入站的 ByteBuf 讀取四個字節(jié),解碼成整形,并添加到一個 List (本例是指 Integer),當不能再添加數(shù)據(jù)到 list 時,它所包含的內(nèi)容就會被發(fā)送到下個 ChannelInboundHandler

Listing 7.1 ByteToMessageDecoder that decodes to Integer

public class ToIntegerDecoder extends ByteToMessageDecoder {  //1

    @Override
    public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
            throws Exception {
        if (in.readableBytes() >= 4) {  //2
            out.add(in.readInt());  //3
        }
    }
}
  1. 實現(xiàn)繼承了 ByteToMessageDecode 用于將字節(jié)解碼為消息
  2. 檢查可讀的字節(jié)是否至少有4個 ( int 是4個字節(jié)長度)
  3. 從入站 ByteBuf 讀取 int , 添加到解碼消息的 List 中

盡管 ByteToMessageDecoder 簡化了這個模式,你會發(fā)現(xiàn)它還是有點煩人,在實際的讀操作(這里 readInt())之前,必須要驗證輸入的 ByteBuf 要有足夠的數(shù)據(jù)。在下一節(jié)中,我們將看看 ReplayingDecoder,一個特殊的解碼器。

章節(jié)5和6中提到,應(yīng)該特別注意引用計數(shù)。對于編碼器和解碼器來說,這個過程非常簡單。一旦一個消息被編碼或解碼它自動被調(diào)用ReferenceCountUtil.release(message) 。如果你稍后還需要用到這個引用而不是馬上釋放,你可以調(diào)用 ReferenceCountUtil.retain(message)。這將增加引用計數(shù),防止消息被釋放。

ReplayingDecoder

ReplayingDecoder 是 byte-to-message 解碼的一種特殊的抽象基類,讀取緩沖區(qū)的數(shù)據(jù)之前需要檢查緩沖區(qū)是否有足夠的字節(jié),使用ReplayingDecoder就無需自己檢查;若ByteBuf中有足夠的字節(jié),則會正常讀?。蝗魶]有足夠的字節(jié)則會停止解碼。

ByteToMessageDecoder 和 ReplayingDecoder

注意到 ReplayingDecoder 繼承自 ByteToMessageDecoder ,所以API 跟表 7.1 是相同的

也正因為這樣的包裝使得 ReplayingDecoder 帶有一定的局限性:

  • 不是所有的標準 ByteBuf 操作都被支持,如果調(diào)用一個不支持的操作會拋出 UnreplayableOperationException
  • ReplayingDecoder 略慢于 ByteToMessageDecoder

如果這些限制是可以接受你可能更喜歡使用 ReplayingDecoder。下面是一個簡單的準則:

如果不引入過多的復(fù)雜性 使用 ByteToMessageDecoder 。否則,使用ReplayingDecoder。

Listing 7.2 ReplayingDecoder

public class ToIntegerDecoder2 extends ReplayingDecoder<Void> {   //1

    @Override
    public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
            throws Exception {
        out.add(in.readInt());  //2
    }
}
  1. 實現(xiàn)繼承自 ReplayingDecoder 用于將字節(jié)解碼為消息
  2. 從入站 ByteBuf 讀取整型,并添加到解碼消息的 List 中

如果我們比較清單7.1和7.2很明顯,實現(xiàn)使用 ReplayingDecoder 更簡單。

更多 Decoder

下面是更加復(fù)雜的使用場景: io.netty.handler.codec.LineBasedFrameDecoder 通過結(jié)束控制符("\n" 或 "\r\n").解析入站數(shù)據(jù)。 io.netty.handler.codec.http.HttpObjectDecoder 用于 HTTP 數(shù)據(jù)解碼

MessageToMessageDecoder

用于從一種消息解碼為另外一種消息(例如,POJO 到 POJO),下表展示了方法:

Table 7.2 MessageToMessageDecoder API

方法名稱描述
decodedecode is the only abstract method you need to implement. It is called for each inbound message to be decoded to another format . The decoded messages are then passed to the next ChannelInboundHandler in the pipeline.
decodeLastThe default implementation provided simply calls decode().This method is called once, when the Channel goes inactive. Override to provide special

handling

將 Integer 轉(zhuǎn)為 String,我們提供了 IntegerToStringDecoder,繼承自 MessageToMessageDecoder。

因為這是一個參數(shù)化的類,實現(xiàn)的簽名是:

public class IntegerToStringDecoder extends MessageToMessageDecoder<Integer>

decode() 方法的簽名是

protected void decode( ChannelHandlerContext ctx,
Integer msg, List<Object> out ) throws Exception

也就是說,入站消息是按照在類定義中聲明的參數(shù)類型(這里是 Integer) 而不是 ByteBuf來解析的。在之前的例子,解碼消息(這里是String)將被添加到List,并傳遞到下個 ChannelInboundHandler。 這是如圖7.2所示。

Figure%207

Figure 7.2 IntegerToStringDecoder

實現(xiàn)如下:

Listing 7.3 MessageToMessageDecoder - Integer to String

public class IntegerToStringDecoder extends
        MessageToMessageDecoder<Integer> { //1

    @Override
    public void decode(ChannelHandlerContext ctx, Integer msg, List<Object> out)
            throws Exception {
        out.add(String.valueOf(msg)); //2
    }
}
  1. 實現(xiàn)繼承自 MessageToMessageDecoder
  2. 通過 String.valueOf() 轉(zhuǎn)換 Integer 消息字符串

正如我們上面指出的,decode()方法的消息參數(shù)的類型是由給這個類指定的泛型的類型(這里是Integer)確定的。

HttpObjectAggregator

更多復(fù)雜的示例,請查看類 io.netty.handler.codec.http.HttpObjectAggregator,繼承自MessageToMessageDecoder

在解碼時處理太大的幀

Netty 是異步框架需要緩沖區(qū)字節(jié)在內(nèi)存中,直到你能夠解碼它們。因此,你不能讓你的解碼器緩存太多的數(shù)據(jù)以免耗盡可用內(nèi)存。為了解決這個共同關(guān)心的問題, Netty 提供了一個 TooLongFrameException ,通常由解碼器在幀太長時拋出。

為了避免這個問題,你可以在你的解碼器里設(shè)置一個最大字節(jié)數(shù)閾值,如果超出,將導(dǎo)致 TooLongFrameException 拋出(并由 ChannelHandler.exceptionCaught() 捕獲)。然后由譯碼器的用戶決定如何處理它。雖然一些協(xié)議,比如 HTTP、允許這種情況下有一個特殊的響應(yīng),有些可能沒有,事件唯一的選擇可能就是關(guān)閉連接。

如清單7.4所示 ByteToMessageDecoder 可以利用 TooLongFrameException 通知其他 ChannelPipeline 中的 ChannelHandler。

Listing 7.4 SafeByteToMessageDecoder encodes shorts into a ByteBuf

public class SafeByteToMessageDecoder extends ByteToMessageDecoder {  //1
    private static final int MAX_FRAME_SIZE = 1024;

    @Override
    public void decode(ChannelHandlerContext ctx, ByteBuf in,
                       List<Object> out) throws Exception {
        int readable = in.readableBytes();
        if (readable > MAX_FRAME_SIZE) { //2
            in.skipBytes(readable);        //3
            throw new TooLongFrameException("Frame too big!");
        }
        // do something
    }
}
  1. 實現(xiàn)繼承 ByteToMessageDecoder 來將字節(jié)解碼為消息
  2. 檢測緩沖區(qū)數(shù)據(jù)是否大于 MAX_FRAME_SIZE
  3. 忽略所有可讀的字節(jié),并拋出 TooLongFrameException 來通知 ChannelPipeline 中的 ChannelHandler 這個幀數(shù)據(jù)超長

這種保護是很重要的,尤其是當你解碼一個有可變幀大小的協(xié)議的時候。

好了,解碼器常見用例的解釋以及 Netty 提供的用于構(gòu)建它們它們的抽象基類就講到這了。但是理解完解碼器只是一方面而已,另一方面,我們還需要完成 Codec API,所以在下一章中我們將介紹編碼器,它是用來轉(zhuǎn)換消息到出站數(shù)據(jù)的。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號