雖然我們一直把解碼器和編碼器作為不同的實體來討論,但你有時可能會發現把入站和出站的數據和信息轉換都放在同一個類中更實用。Netty的抽象編解碼器類就是用于這個目的,他們把一些成對的解碼器和編碼器組合在一起,以此來提供對于字節和消息都相同的操作。(這些類實現了 ChannelInboundHandler 和 ChannelOutboundHandler )。
您可能想知道是否有時候使用單獨的解碼器和編碼器會比使用這些組合類要好,最簡單的答案是,緊密耦合的兩個函數減少了他們的可重用性,但是把他們分開實現就會更容易擴展。當我們研究抽象編解碼器類時,我們也會拿它和對應的獨立的解碼器和編碼器做對比。
### [](https://github.com/waylau/essential-netty-in-action/blob/master/CORE%20FUNCTIONS/Abstract%20Codec%20classes.md#bytetomessagecodec)ByteToMessageCodec
我們需要解碼字節到消息,也許是一個 POJO,然后轉回來。ByteToMessageCodec 將為我們處理這個問題,因為它結合了ByteToMessageDecoder 和 MessageToByteDecoder。表7.5中列出的重要方法。
Table 7.5 ByteToMessageCodec API
| 方法名稱 | 描述 |
| --- | --- |
| decode | This method is called as long as bytes are available to be consumed. It converts the inbound ByteBuf to the specified message format and forwards them to the next ChannelInboundHandler in the pipeline. |
| decodeLast | The default implementation of this method delegates to decode(). It is called only be called once, when the Channel goes inactive. For special handling it can be oerridden. |
| encode | This method is called for each message to be written through the ChannelPipeline. The encoded messages are contained in a ByteBuf which |
什么會是一個好的 ByteToMessageCodec 用例?任何一個請求/響應協議都可能是,例如 SMTP。編解碼器將讀取入站字節并解碼到一個自定義的消息類型 SmtpRequest 。當接收到一個 SmtpResponse 會產生,用于編碼為字節進行傳輸。
### [](https://github.com/waylau/essential-netty-in-action/blob/master/CORE%20FUNCTIONS/Abstract%20Codec%20classes.md#messagetomessagecodec)MessageToMessageCodec
7.3.2節中我們看到的一個例子使用 MessageToMessageEncoder 從一個消息格式轉換到另一個地方。現在讓我們看看 MessageToMessageCodec 是如何處理 單個類 的往返。
在進入細節之前,讓我們看看表7.6中的重要方法。
Table 7.6 Methods of MessageToMessageCodec
| 方法名稱 | 描述 |
| --- | --- |
| decode | This method is called with the inbound messages of the codec and decodes them to messages. Those messages are forwarded to the next ChannelInboundHandler in the ChannelPipeline |
| decodeLast | Default implementation delegates to decode().decodeLast will only be called one time, which is when the Channel goes inactive. If you need special handling here you may override decodeLast() to implement it. |
| encode | The encode method is called for each outbound message to be moved through the ChannelPipeline. The encoded messages are forwarded to the next ChannelOutboundHandler in the pipeline |
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() 則相反。我們在哪里可能使用這樣的編解碼器?
在現實中,這是一個相當常見的用例,往往涉及兩個來回轉換的數據消息傳遞API 。這是常有的事,當我們不得不與遺留或專有的消息格式進行互操作。
如清單7.7所示這樣的可能性。在這個例子中,WebSocketConvertHandler 是一個靜態嵌套類,繼承了參數為 WebSocketFrame(類型為 INBOUND)和 WebSocketFrame(類型為 OUTBOUND)的 MessageToMessageCode
Listing 7.7 MessageToMessageCodec
~~~
public class WebSocketConvertHandler extends MessageToMessageCodec<WebSocketFrame, WebSocketConvertHandler.WebSocketFrame> { //1
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
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
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
public enum FrameType { //5
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;
}
}
}
~~~
1. 編碼 WebSocketFrame 消息轉為 WebSocketFrame 消息
2. 檢測 WebSocketFrame 的 FrameType 類型,并且創建一個新的響應的 FrameType 類型的 WebSocketFrame
3. 通過 instanceof 來檢測正確的 FrameType
4. 自定義消息類型 WebSocketFrame
5. 枚舉類明確了 WebSocketFrame 的類型
### [](https://github.com/waylau/essential-netty-in-action/blob/master/CORE%20FUNCTIONS/Abstract%20Codec%20classes.md#combinedchannelduplexhandler)CombinedChannelDuplexHandler
如前所述,結合解碼器和編碼器在一起可能會犧牲可重用性。為了避免這種方式,并且部署一個解碼器和編碼器到 ChannelPipeline 作為邏輯單元而不失便利性。
關鍵是下面的類:
~~~
public class CombinedChannelDuplexHandler<I extends ChannelInboundHandler,O extends ChannelOutboundHandler>
~~~
這個類是擴展 ChannelInboundHandler 和 ChannelOutboundHandler 參數化的類型。這提供了一個容器,單獨的解碼器和編碼器類合作而無需直接擴展抽象的編解碼器類。我們將在下面的例子說明這一點。首先查看 ByteToCharDecoder ,如清單7.8所示。
Listing 7.8 ByteToCharDecoder
~~~
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());
}
}
}
~~~
1. 繼承 ByteToMessageDecoder
2. 寫 char 到 MessageBuf
decode() 方法從輸入數據中提取兩個字節,并將它們作為一個 char 寫入 List 。(注意,實現擴展 ByteToMessageDecoder 因為它從 ByteBuf 讀取字符。)
現在看一下清單7.9中,把字符轉換為字節的編碼器。
Listing 7.9 CharToByteEncoder
~~~
public class CharToByteEncoder extends
MessageToByteEncoder<Character> { //1
@Override
public void encode(ChannelHandlerContext ctx, Character msg, ByteBuf out)
throws Exception {
out.writeChar(msg); //2
}
}
~~~
1. 繼承 MessageToByteEncoder
2. 寫 char 到 ByteBuf
這個實現繼承自 MessageToByteEncoder 因為他需要編碼 char 消息 到 ByteBuf。這將直接將字符串寫為 ByteBuf。
現在我們有編碼器和解碼器,將他們組成一個編解碼器。見下面的 CombinedChannelDuplexHandler.
Listing 7.10 CombinedByteCharCodec
~~~
public class CombinedByteCharCodec extends CombinedChannelDuplexHandler<ByteToCharDecoder, CharToByteEncoder> {
public CombinedByteCharCodec() {
super(new ByteToCharDecoder(), new CharToByteEncoder());
}
}
~~~
1. CombinedByteCharCodec 的參數是解碼器和編碼器的實現用于處理進站字節和出站消息
2. 傳遞 ByteToCharDecoder 和 CharToByteEncoder 實例到 super 構造函數來委托調用使他們結合起來。
正如你所看到的,它可能是用上述方式來使程序更簡單、更靈活,而不是使用一個以上的編解碼器類。它也可以歸結到你個人喜好或風格。
- Introduction
- 開始
- Netty-異步和數據驅動
- Netty 介紹
- 構成部分
- 關于本書
- 第一個 Netty 應用
- 設置開發環境
- Netty 客戶端/服務端 總覽
- 寫一個 echo 服務器
- 寫一個 echo 客戶端
- 編譯和運行 Echo 服務器和客戶端
- 總結
- Netty 總覽
- Netty 快速入門
- Channel, Event 和 I/O
- 什么是 Bootstrapping 為什么要用
- ChannelHandler 和 ChannelPipeline
- 近距離觀察 ChannelHandler
- 總結
- 核心功能
- Transport(傳輸)
- 案例研究:Transport 的遷移
- Transport API
- 包含的 Transport
- Transport 使用情況
- 總結
- Buffer(緩沖)
- Buffer API
- ByteBuf - 字節數據的容器
- 字節級別的操作
- ByteBufHolder
- ByteBuf 分配
- 總結
- ChannelHandler 和 ChannelPipeline
- ChannelHandler 家族
- ChannelPipeline
- ChannelHandlerContext
- 總結
- Codec 框架
- 什么是 Codec
- Decoder(解碼器)
- Encoder(編碼器)
- 抽象 Codec(編解碼器)類
- 總結
- 提供了的 ChannelHandler 和 Codec
- 使用 SSL/TLS 加密 Netty 程序
- 構建 Netty HTTP/HTTPS 應用
- 空閑連接以及超時
- 解碼分隔符和基于長度的協議
- 編寫大型數據
- 序列化數據
- 總結
- Bootstrap 類型
- 引導客戶端和無連接協議
- 引導服務器
- 從 Channel 引導客戶端
- 在一個引導中添加多個 ChannelHandler
- 使用Netty 的 ChannelOption 和屬性
- 關閉之前已經引導的客戶端或服務器
- 總結
- 引導
- Bootstrap 類型
- 引導客戶端和無連接協議
- 引導服務器
- 從 Channel 引導客戶端
- 在一個引導中添加多個 ChannelHandler
- 使用Netty 的 ChannelOption 和屬性
- 關閉之前已經引導的客戶端或服務器
- 總結
- NETTY BY EXAMPLE
- 單元測試
- 總覽
- 測試 ChannelHandler
- 測試異常處理
- 總結
- WebSocket
- WebSocket 程序示例
- 添加 WebSocket 支持
- 測試程序
- 總結
- SPDY
- SPDY 背景
- 示例程序
- 實現
- 啟動 SpdyServer 并測試
- 總結
- 通過 UDP 廣播事件
- UDP 基礎
- UDP 廣播
- UDP 示例
- EventLog 的 POJO
- 寫廣播器
- 寫監視器
- 運行 LogEventBroadcaster 和 LogEventMonitor
- 總結
- 高級主題
- 實現自定義的編解碼器
- 編解碼器的范圍
- 實現 Memcached 編解碼器
- 了解 Memcached 二進制協議
- Netty 編碼器和解碼器
- 測試編解碼器
- EventLoop 和線程模型
- 線程模型的總覽
- EventLoop
- EventLoop
- I/O EventLoop/Thread 分配細節
- 總結
- 用例1:Droplr Firebase 和 Urban Airship
- 用例2:Facebook 和 Twitter