Netty實戰十之編解碼器框架

編碼和解碼,或者數據從一種特定協議的格式到另外一種格式的轉換。這些任務將由一般稱爲編解碼器的組件來處理。Netty提供了多種組件,簡化了爲了支持普遍的協議而建立自定義的編解碼器的過程。例如,若是你正在構建一個基於Netty的郵件服務器,那麼你將會發現Netty對於編解碼器的支持對於實現POP三、IMAP和SMTP協議來講是多麼的寶貴。web

一、什麼是編解碼器服務器

每一個網絡應用程序都必須定義如何解析在兩個節點之間來回傳輸的原始字節,以及如何將其和目標應用程序的數據格式作相互轉換。這種轉換邏輯由編解碼器處理,編解碼器有編碼器和解碼器組成,它們每種均可以將字節流從一種格式轉換爲另外一種格式。websocket

若是將消息看做是對於特定的應用程序具備具體含義的結構化的字節序列——它的數據。那麼編碼器是將消息轉換爲適合於傳輸的格式(最有可能的就是字節流);而對應的解碼器則是將網絡字節流轉換回應用程序的消息格式。所以,編碼器操做出站數據,而解碼器處理入站數據。網絡

二、解碼器框架

——將字節解碼爲消息——ByteToMessageDecoder和ReplayingDecoder異步

——將一種消息類型解碼爲另外一種——MessageToMessageDecodersocket

由於解碼器是負責將入站數據從一種格式轉換到另外一種格式的,因此知道Netty的解碼器實現了ChannelInboundHandler也不會讓你感到意外。ide

每當須要爲ChannelPipeline中的下一個ChannelInboundHandler轉換入站數據時會用到。此外,得益於ChannelPipeline的設計,能夠將多個解碼器連接在一塊兒,以實現任意複雜的轉換邏輯,這也是Netty是如何支持代碼的模塊化以及複用的例子。模塊化

三、抽象類ByteToMessageDecoder學習

將字節解碼爲消息是一項如此常見的任務,以致於Netty爲他提供了一個抽象的基類:ByteToMessageDecoder。因爲你不可能知道遠程節點是否會一次性地發送一個完整的消息,因此這個類會對入站數據進行緩衝。

下面舉一個如何使用這個類的示例,假設你接收了一個包含簡單int的字節流,每一個int都須要被單獨處理。在這種狀況下,你須要從入站ByteBuf中讀取每一個int,並將它傳遞給ChannelPipeline中的下一個ChannelInboundHandler。爲了解碼這個字節流,你要擴展ByteToMessageDecoder類。(須要注意的是,原始類型int在被添加到List中時,會被自動裝箱爲Integer),以下設計圖。
Netty實戰十之編解碼器框架

每次從入站ByteBuf中讀取4字節,將其解碼爲一個Int,而後將它添加到一個List中。當沒有更多的元素能夠被添加到該List中時,它的內容將會被髮送給下一個ChannelInboundHandler。

public class ToIntegerDecoder extends ByteToMessageDecoder{
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext,                          ByteBuf in, List<Object> out) throws Exception {        //檢查是否至少有4字節可讀(一個int的字節長度)
        if (in.readableBytes() >= 4){            //從入站ByteBuf中讀取一個int,並將其添加到解碼消息的List中
            out.add(in.readInt());
        }
    }
}

雖然ByteToMessageDecoder使得能夠很簡單地實現這種模式,可是你可能會發現,在調用readint()方法前不得不驗證所輸入的ByteBuf是否具備足夠的數據有點繁瑣。

四、抽象類ReplayingDecoder

ReplayingDecoder擴展了ByteToMessageDecoder類,使得咱們沒必要調用readableBytes()方法。它經過使用一個自定義的ByteBuf實現,ReplayingDecoderByteBuf,包裝傳入的ByteBuf實現了這一點,其將在內部執行該調用。

public abstract class ReplayingDecoder extends ByteToMessageDecoder

類型參數S指定了用於狀態管理的類型,其中Void表明不須要狀態管理。如下代碼展現了基於ReplayingDecoder從新實現ToIntegerDecoder。

public class ToIntegerDecoder2 extends ReplayingDecoder<Void>{
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext,                          ByteBuf in, List<Object> out) throws Exception {
        out.add(in.readInt());
    }
}

和以前同樣,從ByteBuf中提取的int將會被添加到List中,若是沒有足夠的字節可用,這個readInt()方法的實現將會拋出一個Error,其將在基類中被捕獲並處理。當有更多的數據可供讀取時,該decode()方法將會被再次調用。

請注意ReplayingDecoderByteBuf的下面這些方面:

——並非全部的ByteBuf操做都被支持,若是調用了一個不被支持的方法,將會拋出一個UnsupportedOperationException

——ReplayingDecoder稍慢於ByteToMessageDecoder

——若是使用ByteToMessageDecoder不會引入太多的複雜性,那麼請使用它;不然,請使用ReplayingDecoder

五、抽象類MessageToMessageDecoder

public abstract class MessageToMessageDecoder extends ChannelInboundHandlerAdapter

類型參數I指定了decode()方法的輸入參數msg的類型,它是你必須實現的惟一方法。

在這個示例中,咱們將編寫一個IntegerToStringDecoder解碼器來擴展MessageToMessageDecoder<Integer>。它的decode()方法會把Integer參數轉換爲它的String表示,並將擁有下列簽名:

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

和以前同樣,解碼的String將被添加到傳出的List中,並轉發給下一個ChannelInboundHandler

public class IntegerToStringDecoder extends MessageToMessageDecoder<Integer>{
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext,                          Integer msg, List<Object> out) throws Exception {        //將Integer消息轉換爲它的String表示,並將其添加到輸出的List中
        out.add(String.valueOf(msg));
    }
}

六、TooLongFrameException類

因爲Netty是一個異步框架,因此須要在字節能夠解碼以前在內存中緩衝他們,所以,不能讓解碼器緩衝大量的數據以致於耗盡可用的內存。爲了解除這個常見的顧慮,Netty提供了TooLongFrameException類,其將由解碼器在幀超出指定的大小限制時拋出。

爲了不這種狀況,你能夠設置一個最大字節數的伐值,若是超過,則會致使拋出一個TooLongFrameException,如何處理該異常則徹底取決於解碼器的用戶。某些協議(HTTP)可能容許你返回一個特殊的響應,而在其餘的狀況下,惟一的選擇可能就是關閉對應的鏈接。

如下代碼展現了ByteToMessageDecoder是如何使用TooLongFrameException來通知ChannelPipeline中的其餘ChannelHandler發生了幀大小溢出的。須要注意的是,若是你正在使用一個可變幀大小的協議,那麼這種保護措施將是尤爲重要的。

public class SafeByteToMessageDecoder extends ByteToMessageDecoder{
    private static final int MAX_FRAME_SIZE = 1024;    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext,                          ByteBuf in, List<Object> out) throws Exception {
        int readable = in.readableBytes();        //檢查緩衝區中是否有超過MAX_FRAME_SIZE個字節
        if (readable > MAX_FRAME_SIZE){            //跳過全部的可讀字節,拋出TooLongFrameException並通知ChannelHandler
            in.skipBytes(readable);            throw new TooLongFrameException("Frame too big!");
        }        //DO something
    }
}

七、抽象類MessageToByteEncoder

這個類只有一個方法,而解碼器有兩個。緣由是解碼器一般須要在Channel關閉以後產生最後一個消息(decodeLast()方法),這顯然不適用於編碼器的場景——在鏈接關閉以後仍然產生一個消息是毫無心義的。

ShortToByteEncoder,其接受一個Short類型的實例做爲消息,將它編碼爲Short的原始類型值,並將它寫入ByteBuf中,其將隨後被轉發給ChannelPipeline中的下一個CHannelOutboundHandler。每一個傳出的Short值都將會佔用ByteBuf中的2字節。

public class ShortToByteEncoder extends MessageToByteEncoder<Short>{
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext,                          Short msg, ByteBuf out) throws Exception {        //將Short寫入ByteBuf中
        out.writeShort(msg);
    }
}

Netty提供了一些專門化的MessageToByteEncoder,你能夠基於他們實現本身的編碼器。WebSocket08FrameEncoder類提供了一個很好的實例。你能夠在io.netty.handler.codec.http.websocket包中找到它。

八、抽象類MessageToMessageEncoder

咱們將展現對於出站數據將如何從一種消息編碼爲另外一種。MessageToMessageEncoder類的encoder()方法提供了這種能力。

如下代碼,編碼器將每一個出站Integer的String表示添加到了該List中。

public class IntegerToStringEncoder extends MessageToMessageEncoder<Integer>{
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext,                          Integer msg, List<Object> out) throws Exception {        //將Integer轉換爲String,並將其添加到List中
        out.add(String.valueOf(msg));
    }
}

九、抽象的編解碼器類

在同一個類中管理入站和出站數據和消息的轉換是頗有用的。Netty的抽象編解碼器類正好用於這個目的,由於它們每一個都將捆綁一個解碼器/編碼器對,以處理咱們一直在學習的這兩種類型的操做。

經過儘量地將這兩種功能分開,最大化了代碼的可重用性和可擴展性,這是Netty設計的一個基本原則。

十、抽象類ByteToMessageCodec

場景:咱們須要將字節編碼爲某種形式的消息,多是POJO,隨後再次對它進行編碼。ByteToMessageCodec將爲咱們處理好了這一切,由於它結合了ByteToMessageDecoder以及他的逆向MessageToByteEncoder。

任何的請求/響應協議均可以做爲使用ByteToMessageCodec的理想選擇,例如,在某個SMTP的實現中,編解碼器將讀取傳入字節,並將它們解碼爲一個自定義的消息類型,如SmtpRequest。而在接收端,當一個響應被建立時,將會產生一個SmtpResponse,其將被編碼回字節以便進行傳輸。

十一、CombinedChannelDuplexHandler類

結合一個解碼器和編碼器可能會對可重用性形成影響。可是,有一種方法即可以避免這種懲罰,又不會犧牲將一個解碼器和一個編碼器做爲一個單獨的單元部署所帶來的的便利性。CombinedChannelDuplexHandler提供了這個解決方案,其聲明爲:

public class CombinedChannelDuplexHandler <I extends ChannelInboundHandler, O extends ChannelOutboundHandler>

這個類充當了ChannelInboundHandler和ChannelOutboundHandler(該類的類型參數I和O)的容器。經過提供分別繼承瞭解碼器類和編碼器類的類型,咱們能夠實現一個編解碼器,而又沒必要直接擴展抽象的編解碼器類。

首先,讓咱們研究代碼中的ByteToCharDecoder。注意,該實現擴展了ByteToMessageDecoder,由於它要從ByteBuf中讀取字符。

public class ByteToCharDecoder extends ByteToMessageDecoder{
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext,                          ByteBuf in, List<Object> out) throws Exception {        while (in.readableBytes() >= 2){            //將一個或者多個Character對象添加到傳出的List中
            out.add(in.readChar());
        }
    }
}

這裏的decode()方法一次將從ByteBuf中提取2字節,並將它們做爲char寫入到List中,其將會被自動裝箱爲Character對象。

如下代碼,包含了CharToByteEncoder,他能將Character轉換回字節。這個類擴展了MessageToByteEncoder,由於它須要將char消息編碼到ByteBuf中,這是經過直接寫入ByteBuf作到的。

public class CharToByteEncoder extends MessageToByteEncoder<Character>{
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext,                          Character msg, ByteBuf out) throws Exception {        //將Character解碼爲char,並將其寫入到出站ByteBuf中
        out.writeChar(msg);
    }
}

既然咱們有了解碼器和編碼器,咱們將會結合它們來構建 一個編解碼器。如如下代碼所示。

在某些狀況下,經過這種方式結合實現相對於使用編解碼器類的方式來講可能更加的簡單也更加的靈活。

相關文章
相關標籤/搜索