Netty:核心功能Codec 框架

編碼和解碼——數據從一種特定協議格式到另一種格式的轉換。這種處理模式是由通常被稱爲“codecs(編解碼器)”的組件來處理的。Netty提供了一些組件,利用它們可以很容易地爲各種不同協議編寫編解碼器。例如,如果您正在構建一個基於 Netty 的郵件服務器,你可以使用POP3, IMAP 和 SMTP的現成的實現.

1 基本概念

我們在編寫一個網絡應用程序的時候需要實現某種 codec (編解碼器),有了codec就能夠將原始字節數據與目標程序數據的格式相互轉化。我們知道數據在網絡中的傳播都是以字節碼的數據形式進行的,codec的組成部分有兩個,分別是:decoder(解碼器)和encoder(編碼器)
解碼器負責將消息從字節或其他序列形式轉成指定的消息對象,編碼器的功能則相反;解碼器負責處理“入站”數據,編碼器負責處理“出站”數據。編碼器和解碼器的結構很簡單,消息被編碼後解碼後會自動通過ReferenceCountUtil.release(message)釋放,如果不想釋放消息可以使用ReferenceCountUtil.retain(message),這將會使引用數量增加而沒有消息發佈,大多數時候不需要這麼做。

2 Decoder(解碼器)

Netty 提供了豐富的解碼器抽象基類,我們可以很容易的實現這些基類來自定義解碼器。主要分兩類:
解碼字節到消息(ByteToMessageDecoder 和 ReplayingDecoder)
解碼消息到消息(MessageToMessageDecoder)

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

2.1 ByteToMessageDecoder

ByteToMessageDecoder 是用於將字節轉爲消息(或其他字節序列)。你不能確定遠端是否會一次發送完一個完整的“信息”,因此這個類會緩存入站的數據,直到準備好了用於處理。表7.1說明了它的兩個最重要的方法。
在這裏插入圖片描述
每次從入站的 ByteBuf 讀取四個字節,解碼成整形,並添加到一個 List (本例是指 Integer),當不能再添加數據到 list 時,它所包含的內容就會被髮送到下個 ChannelInboundHandler.

public class ToIntegerDecoder extends ByteToMessageDecoder {  //1實現繼承了 ByteToMessageDecode 用於將字節解碼爲消息

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

儘管 ByteToMessageDecoder 簡化了這個模式,你會發現在實際的讀操作(這裏 readInt())之前,必須要驗證輸入的 ByteBuf 要有足夠的數據。一個特殊的解碼器 ReplayingDecoder
ReplayingDecoder 是 byte-to-message 解碼的一種特殊的抽象基類,讀取緩衝區的數據之前需要檢查緩衝區是否有足夠的字節,使用ReplayingDecoder就無需自己檢查;若ByteBuf中有足夠的字節,則會正常讀取;若沒有足夠的字節則會停止解碼.

public class ToIntegerDecoder2 extends ReplayingDecoder<Void> {   //1實現繼承自 ReplayingDecoder 用於將字節解碼爲消息

    @Override
    public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
            throws Exception {
        out.add(in.readInt());  //2從入站 ByteBuf 讀取整型,並添加到解碼消息的 List 中
    }
}

2.2 MessageToMessageDecoder

用於從一種消息解碼爲另外一種消息(例如,POJO 到 POJO),核心方法和ByteToMessageDecoder類似.

// 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
    }
}

其中,decode()方法的消息參數的類型是由給這個類指定的泛型的類型(這裏是Integer)確定的。

3 Encode(編碼器)

在之前的章節中,我們對encoder有了定義,它是用來把出站數據從一種格式轉換到另外一種格式,因此它實現了 ChanneOutboundHandler 。就像decoder一樣,Netty 也爲你提供了一組類來寫 encoder ,當然這些類提供的是與 decoder 完全相反的方法,如下所示:
從消息到字節進行編碼
從消息到消息進行編碼

3.1 MessageToByteEncoder

在這裏插入圖片描述

在這裏插入圖片描述

public class ShortToByteEncoder extends
        MessageToByteEncoder<Short> {  //1實現繼承自 MessageToByteEncoder
    @Override
    public void encode(ChannelHandlerContext ctx, Short msg, ByteBuf out)
            throws Exception {
        out.writeShort(msg);  //2寫 Short 到 ByteBuf
    }
}

3.2 MessageToMessageEncoder

與MessageToByteEncoder類似只不過方法encode的方法簽名不一樣.

4 Codec(編解碼器)類

Netty中的抽象Codec(編解碼器)類, 它們成對地組合解碼器和編碼器,以此提供對於字節和消息都相同的操作(這些類實現了ChannelInboundHandler 和 ChannelOutboundHandler )。

您可能想知道是否有時候使用單獨的解碼器和編碼器會比使用這些組合類要好,最簡單的答案是,緊密耦合的兩個函數減少了他們的可重用性,但是把他們分開實現就會更容易擴展。

4.1 ByteToMessageCodec

我們需要解碼字節到消息( POJO),然後轉回來。ByteToMessageCodec 將爲我們處理這個問題,因爲它結合了ByteToMessageDecoder 和MessageToByteEncoder。
在這裏插入圖片描述
一個好的 ByteToMessageCodec 用例會是什麼?任何一個請求/響應協議都可能是,例如 SMTP。編解碼器將讀取入站字節並解碼到一個自定義的消息類型 SmtpRequest 。當接收到一個 SmtpResponse 會將其編碼爲字節進行傳輸。

4.2 MessageToMessageCodec

在這裏插入圖片描述

MessageToMessageCodec 是一個參數化的類,定義如下:

public abstract class MessageToMessageCodec<INBOUND,OUTBOUND>

上面所示的完整簽名的方法都是這樣的

protected abstract void encode(ChannelHandlerContext ctx,
OUTBOUND msg, List<Object> out)
protected abstract void decode(ChannelHandlerContext ctx,
INBOUND msg, List<Object> out)

encode() 處理出站消息類型 OUTBOUND 到 INBOUND,而 decode() 則相反.

如清單7.7所示,在這個例子中,WebSocketConvertHandler 是一個靜態嵌套類,繼承了參數爲 WebSocketFrame(類型爲 INBOUND)和 WebSocketFrame(類型爲 OUTBOUND)的 MessageToMessageCode.

public class WebSocketConvertHandler extends MessageToMessageCodec<WebSocketFrame, WebSocketConvertHandler.WebSocketFrame> {  //1編碼 WebSocketFrame 消息轉爲 WebSocketFrame 消息

    public static final WebSocketConvertHandler INSTANCE = new WebSocketConvertHandler();

    @Override
    protected void encode(ChannelHandlerContext ctx, WebSocketFrame msg, List<Object> out) throws Exception {   
        ByteBuf payload = msg.getData().duplicate().retain();
        switch (msg.getType()) {   //2檢測 WebSocketFrame 的 FrameType 類型,並且創建一個新的響應的 FrameType 類型的 WebSocketFrame
            case BINARY:
                out.add(new BinaryWebSocketFrame(payload));
                break;
            case TEXT:
                out.add(new TextWebSocketFrame(payload));
                break;
            case CLOSE:
                out.add(new CloseWebSocketFrame(true, 0, payload));
                break;
            case CONTINUATION:
                out.add(new ContinuationWebSocketFrame(payload));
                break;
            case PONG:
                out.add(new PongWebSocketFrame(payload));
                break;
            case PING:
                out.add(new PingWebSocketFrame(payload));
                break;
            default:
                throw new IllegalStateException("Unsupported websocket msg " + msg);
        }
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, io.netty.handler.codec.http.websocketx.WebSocketFrame msg, List<Object> out) throws Exception {
        if (msg instanceof BinaryWebSocketFrame) {  //3通過 instanceof 來檢測正確的 FrameType
            out.add(new WebSocketFrame(WebSocketFrame.FrameType.BINARY, msg.content().copy()));
        } else if (msg instanceof CloseWebSocketFrame) {
            out.add(new WebSocketFrame(WebSocketFrame.FrameType.CLOSE, msg.content().copy()));
        } else if (msg instanceof PingWebSocketFrame) {
            out.add(new WebSocketFrame(WebSocketFrame.FrameType.PING, msg.content().copy()));
        } else if (msg instanceof PongWebSocketFrame) {
            out.add(new WebSocketFrame(WebSocketFrame.FrameType.PONG, msg.content().copy()));
        } else if (msg instanceof TextWebSocketFrame) {
            out.add(new WebSocketFrame(WebSocketFrame.FrameType.TEXT, msg.content().copy()));
        } else if (msg instanceof ContinuationWebSocketFrame) {
            out.add(new WebSocketFrame(WebSocketFrame.FrameType.CONTINUATION, msg.content().copy()));
        } else {
            throw new IllegalStateException("Unsupported websocket msg " + msg);
        }
    }

    public static final class WebSocketFrame {  //4自定義消息類型 WebSocketFrame
        public enum FrameType {        //5枚舉類明確了 WebSocketFrame 的類型
            BINARY,
            CLOSE,
            PING,
            PONG,
            TEXT,
            CONTINUATION
        }

        private final FrameType type;
        private final ByteBuf data;
        public WebSocketFrame(FrameType type, ByteBuf data) {
            this.type = type;
            this.data = data;
        }

        public FrameType getType() {
            return type;
        }

        public ByteBuf getData() {
            return data;
        }
    }
}

4.3 CombinedChannelDuplexHandler

如前所述,結合解碼器和編碼器在一起可能會犧牲可重用性。爲了避免這種方式,並且部署一個解碼器和編碼器到 ChannelPipeline 作爲邏輯單元而不失便利性。關鍵是下面的類:

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

這個類是擴展 ChannelInboundHandler 和 ChannelOutboundHandler 參數化的類型。這提供了一個容器,單獨的解碼器和編碼器類合作而無需直接擴展抽象的編解碼器類。

解碼器

public class ByteToCharDecoder extends
        ByteToMessageDecoder { //1

    @Override
    public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
            throws Exception {
        if (in.readableBytes() >= 2) {  //2
            out.add(in.readChar());
        }
    }
}

編碼器

public class CharToByteEncoder extends
        MessageToByteEncoder<Character> { //1

    @Override
    public void encode(ChannelHandlerContext ctx, Character msg, ByteBuf out)
            throws Exception {
        out.writeChar(msg);   //2
    }
}

現在我們有編碼器和解碼器,將他們組成一個編解碼器。見下面的 CombinedChannelDuplexHandler.

public class CombinedByteCharCodec extends CombinedChannelDuplexHandler<ByteToCharDecoder, CharToByteEncoder> {
    public CombinedByteCharCodec() {
        super(new ByteToCharDecoder(), new CharToByteEncoder());
    }
}

CombinedByteCharCodec 的參數是解碼器和編碼器的實現用於處理進站字節和出站消息.
傳遞 ByteToCharDecoder 和 CharToByteEncoder 實例到 super 構造函數來委託調用使他們結合起來。
正如你所看到的,它可能是用上述方式來使程序更簡單、更靈活,而不是使用一個以上的編解碼器類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章