本章主要內容
- 解碼器、編碼器以及編解碼器的概述
- Netty的編解碼器類
就像很多標準的架構模式都被各種專用框架所支持一樣,常見的數據處理模式往往也是目標實現的很好的候選對象,它可以節省開發人員大量的時間和精力。
當然這也適應於本章的主題:編碼和解碼,或者數據從一種特定協議的格式到另一種格式的轉換。這些任務將由通常稱為編解碼器的組件來處理。Netty提供了多種組件,簡化了為了支持廣泛的協議而創建自定義的編解碼器的過程。例如,如果你正在構建一個基於Netty的郵件服務器,那麼你將會發現Netty對於編解碼器的支持對於實現POP3、IMAP和SMTP協議來說是多麼的寶貴。
10.1 什麼是編解碼器
每個網絡應用程序都必須定義如何解析在兩個節點之間來回傳輸的原始字節,以及如何將其和目標應用程序的數據格式做相互轉換。這種轉換邏輯由編解碼器處理,編解碼器由編碼器和解碼器組成,它們每種都可以將字節流從一種格式轉換為另一種格式。那麼它們的區別是什麼呢?
如果將消息看作是對於特定的應用程序具有具體含義的結構化的字節序列——它的數據。那麼編碼器是將消息轉換為適合於傳輸的格式(最有可能的就是字節流);而對應的解碼器則是將網絡字節流轉換回應用程序的消息格式。因此,編碼器操作出站數據,而解碼器處理入站數據。
記住這些背景信息,接下來讓我們研究一下Netty所提供的用於實現這兩種組件的類。
10.2 解碼器
在這一節中,我們將研究Netty所提供的解碼器類,並提供關於何時以及如何使用它們的具體示例。這些類覆蓋了兩個不同的用例:
- 將字節解碼為消息——
ByteToMessageDecoder
和ReplayingDecoder
; - 將一種消息類型解碼為另一種——
MessageToMessageDecoder
。
因為解碼器是負責將入站數據從一種格式轉換到另一種格式的,所以知道Netty的解碼器實現了ChannelInboundHandler
也不會讓你感到意外。
什麼時候會用到解碼器呢?很簡單:每當需要為ChannelPipeline
中的下一個Channel-InboundHandler
轉換入站數據時會用到。此外,得益於ChannelPipeline
的設計,可以將多個解碼器鏈接在一起,以實現任意複雜的轉換邏輯,這也是Netty是如何支持代碼的模塊化以及復用的一個很好的例子。
10.2.1 抽像類ByteToMessageDecoder
將字節解碼為消息(或者另一個字節序列)是一項如此常見的任務,以至於Netty為它提供了一個抽像的基類:ByteToMessageDecoder
。由於你不可能知道遠程節點是否會一次性地發送一個完整的消息,所以這個類會對入站數據進行緩衝,直到它準備好處理。表10-1解釋了它最重要的兩個方法。
表10-1 ByteToMessageDecoder
API
方 法
描 述
decode(
ChannelHandlerContext ctx,
ByteBuf in,
List<Object> out)
這是你必須實現的唯一抽像方法。decode
方法被調用時將會傳入一個包含了傳入數據的ByteBuf
,以及一個用來添加解碼消息的List
。對這個方法的調用將會重複進行,直到確定沒有新的元素被添加到該List
,或者該ByteBuf
中沒有更多可讀取的字節時為止。然後,如果該List
不為空,那麼它的內容將會被傳遞給ChannelPipeline
中的下一個ChannelInboundHandler
decodeLast(
ChannelHandlerContext ctx,
ByteBuf in,
List<Object> out)
Netty提供的這個默認實現只是簡單地調用了decode
方法。當Channel
的狀態變為非活動時,這個方法將會被調用一次。可以重寫該方法以提供特殊的處理[1]
下面舉一個如何使用這個類的示例,假設你接收了一個包含簡單int
的字節流,每個int
都需要被單獨處理。在這種情況下,你需要從入站ByteBuf
中讀取每個int
,並將它傳遞給ChannelPipeline
中的下一個ChannelInboundHandler
。為了解碼這個字節流,你要擴展ByteToMessageDecoder
類。(需要注意的是,原始類型的int
在被添加到List
中時,會被自動裝箱為Integer
。)
該設計如圖10-1所示。
每次從入站ByteBuf
中讀取4字節,將其解碼為一個int
,然後將它添加到一個List
中。當沒有更多的元素可以被添加到該List
中時,它的內容將會被發送給下一個Channel-InboundHandler
。
圖10-1 ToIntegerDecoder
代碼清單10-1展示了ToIntegerDecoder
的代碼。
代碼清單10-1 ToIntegerDecoder
類擴展了ByteToMessageDecoder
public class ToIntegerDecoder extends ByteToMessageDecoder { ← -- 擴展ByteToMessage-Decoder 類,以將字節解碼為特定的格式
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in,
List<Object> out) throws Exception {
if (in.readableBytes >= 4) { ← -- 檢查是否至少有4字節可讀(一個int的字節長度)
out.add(in.readInt); ← -- 從入站ByteBuf 中讀取一個int,並將其添加到解碼消息的List 中
}
}
}
雖然ByteToMessageDecoder
使得可以很簡單地實現這種模式,但是你可能會發現,在調用readInt
方法前不得不驗證所輸入的ByteBuf
是否具有足夠的數據有點繁瑣。在下一節中,我們將討論ReplayingDecoder
,它是一個特殊的解碼器,以少量的開銷消除了這個步驟。
編解碼器中的引用計數
正如我們在第5章和第6章中所提到的,引用計數需要特別的注意。對於編碼器和解碼器來說,其過程也是相當的簡單:一旦消息被編碼或者解碼,它就會被
ReferenceCountUtil.release(message)
調用自動釋放。如果你需要保留引用以便稍後使用,那麼你可以調用ReferenceCountUtil.retain(message)
方法。這將會增加該引用計數,從而防止該消息被釋放。
10.2.2 抽像類ReplayingDecoder
ReplayingDecoder
擴展了ByteToMessageDecoder
類(如代碼清單10-1所示),使得我們不必調用readableBytes
方法。它通過使用一個自定義的ByteBuf
實現,ReplayingDecoderByteBuf
,包裝傳入的ByteBuf
實現了這一點,其將在內部執行該調用[2]。
這個類的完整聲明是:
public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder
類型參數S
指定了用於狀態管理的類型,其中Void
代表不需要狀態管理。代碼清單10-2展示了基於ReplayingDecoder
重新實現的ToIntegerDecoder
。
代碼清單10-2 ToIntegerDecoder2
類擴展了ReplayingDecoder
public class ToIntegerDecoder2 extends ReplayingDecoder<Void> { ← -- 擴展Replaying-Decoder<Void>以將字節解碼為消息
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in, ← -- 傳入的ByteBuf 是ReplayingDecoderByteBuf
List<Object> out) throws Exception {
out.add(in.readInt); ← -- 從入站ByteBuf 中讀取一個int,並將其添加到解碼消息的List 中
}
}
和之前一樣,從ByteBuf
中提取的int
將會被添加到List
中。如果沒有足夠的字節可用,這個readInt
方法的實現將會拋出一個Error
[3],其將在基類中被捕獲並處理。當有更多的數據可供讀取時,該decode
方法將會被再次調用。(參見表10-1中關於decode
方法的描述。)
請注意ReplayingDecoderByteBuf
的下面這些方面:
- 並不是所有的
ByteBuf
操作都被支持,如果調用了一個不被支持的方法,將會拋出一個UnsupportedOperationException
; ReplayingDecoder
稍慢於ByteToMessageDecoder
。
如果對比代碼清單10-1和代碼清單10-2,你會發現後者明顯更簡單。示例本身是很基本的,所以請記住,在真實的、更加複雜的情況下,使用一種或者另一種作為基類所帶來的差異可能是很顯著的。這裡有一個簡單的準則:如果使用ByteToMessageDecoder
不會引入太多的複雜性,那麼請使用它;否則,請使用ReplayingDecoder
。
更多的解碼器
下面的這些類處理更加複雜的用例:
io.netty.handler.codec.LineBasedFrameDecoder
——這個類在Netty內部也有使用,它使用了行尾控制字符(\n
或者\r\n
)來解析消息數據;
io.netty.handler.codec.http.HttpObjectDecoder
——一個HTTP數據的解碼器。在
io.netty.handler.codec
子包下面,你將會發現更多用於特定用例的編碼器和解碼器實現。更多有關信息參見Netty的Javadoc。
10.2.3 抽像類MessageToMessageDecoder
在這一節中,我們將解釋如何使用下面的抽像基類在兩個消息格式之間進行轉換(例如,從一種POJO類型轉換為另一種):
public abstract class MessageToMessageDecoder<I>
extends ChannelInboundHandlerAdapter
類型參數I
指定了decode
方法的輸入參數msg
的類型,它是你必須實現的唯一方法。表10-2展示了這個方法的詳細信息。
表10-2 MessageToMessageDecoder
API
方 法
描 述
decode(
ChannelHandlerContext ctx,
I msg,
List<Object> out)
對於每個需要被解碼為另一種格式的入站消息來說,該方法都將會被調用。解碼消息隨後會被傳遞給ChannelPipeline
中的下一個ChannelInboundHandler
在這個示例中,我們將編寫一個IntegerToStringDecoder
解碼器來擴展MessageTo-MessageDecoder<Integer>
。它的decode
方法會把Integer
參數轉換為它的String
表示,並將擁有下列簽名:
public void decode( ChannelHandlerContext ctx,
Integer msg, List<Object> out ) throws Exception
和之前一樣,解碼的String
將被添加到傳出的List
中,並轉發給下一個ChannelInboundHandler
。
該設計如圖10-2所示。
圖10-2 IntegerToStringDecoder
代碼清單10-3給出了IntegerToStringDecoder
的實現。
代碼清單10-3 IntegerToStringDecoder
類
public class IntegerToStringDecoder extends
MessageToMessageDecoder<Integer> { ← -- 擴展了MessageToMessageDecoder<Integer>
@Override
public void decode(ChannelHandlerContext ctx, Integer msg
List<Object> out) throws Exception {
out.add(String.valueOf(msg)); ← -- 將Integer 消息轉換為它的String 表示,並將其添加到輸出的List 中
}
}
HttpObjectAggregator
有關更加複雜的例子,請研究
io.netty.handler.codec.http.HttpObjectAggregator
類,它擴展了MessageToMessageDecoder<HttpObject>
。
10.2.4 TooLongFrameException類
由於Netty是一個異步框架,所以需要在字節可以解碼之前在內存中緩衝它們。因此,不能讓解碼器緩衝大量的數據以至於耗盡可用的內存。為了解除這個常見的顧慮,Netty提供了TooLongFrameException
類,其將由解碼器在幀超出指定的大小限制時拋出。
為了避免這種情況,你可以設置一個最大字節數的閾值,如果超出該閾值,則會導致拋出一個TooLongFrameException
(隨後會被ChannelHandler.exceptionCaught
方法捕獲)。然後,如何處理該異常則完全取決於該解碼器的用戶。某些協議(如HTTP)可能允許你返回一個特殊的響應。而在其他的情況下,唯一的選擇可能就是關閉對應的連接。
代碼清單10-4展示了ByteToMessageDecoder
是如何使用TooLongFrameException
來通知ChannelPipeline
中的其他ChannelHandler
發生了幀大小溢出的。需要注意的是,如果你正在使用一個可變幀大小的協議,那麼這種保護措施將是尤為重要的。
代碼清單10-4 TooLongFrameException
public class SafeByteToMessageDecoder extends ByteToMessageDecoder { ← -- 擴展ByteToMessageDecoder以將字節解碼為消息
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) { ← -- 檢查緩衝區中是否有超過MAX_FRAME_SIZE個字節
in.skipBytes(readable); ← -- 跳過所有的可讀字節,拋出TooLongFrame-Exception 並通知ChannelHandler
throw new TooLongFrameException("Frame too big!");
}
// do something
...
}
}
到目前為止,我們已經探討了解碼器的常規用例,以及Netty所提供的用於構建它們的抽像基類。但是解碼器只是硬幣的一面。硬幣的另一面是編碼器,它將消息轉換為適合於傳出傳輸的格式。這些編碼器完備了編解碼器API,它們將是我們的下一個主題。
10.3 編碼器
回顧一下我們先前的定義,編碼器實現了ChannelOutboundHandler
,並將出站數據從一種格式轉換為另一種格式,和我們方才學習的解碼器的功能正好相反。Netty提供了一組類,用於幫助你編寫具有以下功能的編碼器:
- 將消息編碼為字節;
- 將消息編碼為消息[4]。
我們將首先從抽像基類MessageToByteEncoder
開始來對這些類進行考察。
10.3.1 抽像類MessageToByteEncoder
前面我們看到了如何使用ByteToMessageDecoder
來將字節轉換為消息。現在我們將使用MessageToByteEncoder
來做逆向的事情。表10-3展示了該API。
表10-3 MessageToByteEncoder
API
方 法
描 述
encode(
ChannelHandlerContext ctx,
I msg,
ByteBuf out)
encode
方法是你需要實現的唯一抽像方法。它被調用時將會傳入要被該類編碼為ByteBuf
的(類型為I
的)出站消息。該ByteBuf
隨後將會被轉發給ChannelPipeline
中的下一個ChannelOutboundHandler
你可能已經注意到了,這個類只有一個方法,而解碼器有兩個。原因是解碼器通常需要在Channel
關閉之後產生最後一個消息(因此也就有了decod
eLast方法)。這顯然不適用於編碼器的場景——在連接被關閉之後仍然產生一個消息是毫無意義的。
圖10-3展示了ShortToByteEncoder
,其接受一個Short
類型的實例作為消息,將它編碼為Short
的原始類型值,並將它寫入ByteBuf
中,其將隨後被轉發給ChannelPipeline
中的下一個ChannelOutboundHandler
。每個傳出的Short
值都將會佔用ByteBuf
中的2字節。
ShortToByteEncoder
的實現如代碼清單10-5所示。
代碼清單10-5 ShortToByteEncoder
類
public class ShortToByteEncoder extends MessageToByteEncoder<Short> { ← -- 擴展了MessageToByteEncoder
@Override
public void encode(ChannelHandlerContext ctx, Short msg, ByteBuf out)
throws Exception {
out.writeShort(msg); ← -- 將Short 寫入ByteBuf 中
}
}
Netty提供了一些專門化的MessageToByteEncoder
,你可以基於它們實現自己的編碼器。WebSocket08FrameEncoder
類提供了一個很好的實例。你可以在io.netty.handler. codec.http.websocketx
包中找到它。
圖10-3 ShortToByteEncoder
10.3.2 抽像類MessageToMessageEncoder
你已經看到了如何將入站數據從一種消息格式解碼為另一種。為了完善這幅圖,我們將展示對於出站數據將如何從一種消息編碼為另一種。MessageToMessageEncoder
類的encode
方法提供了這種能力,如表10-4所示。
表10-4 MessageToMessageEncoder
API
名 稱
描 述
encode(
ChannelHandlerContext ctx,
I msg,
List<Object> out)
這是你需要實現的唯一方法。每個通過write
方法寫入的消息都將會被傳遞給encode
方法,以編碼為一個或者多個出站消息。隨後,這些出站消息將會被轉發給ChannelPipeline
中的下一個ChannelOutboundHandler
為了演示,代碼清單10-6使用IntegerToStringEncoder
擴展了MessageToMessage-Encoder
。其設計如圖10-4所示。
圖10-4 IntegerToStringEncoder
如代碼清單10-6所示,編碼器將每個出站Integer
的String
表示添加到了該List
中。
代碼清單10-6 IntegerToStringEncoder
類
public class IntegerToStringEncoder
extends MessageToMessageEncoder<Integer> { ← -- 擴展了MessageToMessageEncoder
@Override
public void encode(ChannelHandlerContext ctx, Integer msg
List<Object> out) throws Exception {
out.add(String.valueOf(msg)); ← -- 將Integer 轉換為String,並將其添加到List 中
}
}
關於有趣的MessageToMessageEncoder
的專業用法,請查看io.netty.handler. codec.protobuf.ProtobufEncoder
類,它處理了由Google的Protocol Buffers規範所定義的數據格式。
10.4 抽像的編解碼器類
雖然我們一直將解碼器和編碼器作為單獨的實體討論,但是你有時將會發現在同一個類中管理入站和出站數據和消息的轉換是很有用的。Netty的抽像編解碼器類正好用於這個目的,因為它們每個都將捆綁一個解碼器/編碼器對,以處理我們一直在學習的這兩種類型的操作。正如同你可能已經猜想到的,這些類同時實現了ChannelInboundHandler
和ChannelOutboundHandler
接口。
為什麼我們並沒有一直優先於單獨的解碼器和編碼器使用這些復合類呢?因為通過盡可能地將這兩種功能分開,最大化了代碼的可重用性和可擴展性,這是Netty設計的一個基本原則。
在我們查看這些抽像的編解碼器類時,我們將會把它們與相應的單獨的解碼器和編碼器進行比較和參照。
10.4.1 抽像類ByteToMessageCodec
讓我們來研究這樣的一個場景:我們需要將字節解碼為某種形式的消息,可能是POJO,隨後再次對它進行編碼。ByteToMessageCodec
將為我們處理好這一切,因為它結合了ByteToMessageDecoder
以及它的逆向——MessageToByteEncoder
。表10-5列出了其中重要的方法。
任何的請求/響應協議都可以作為使用ByteToMessageCodec
的理想選擇。例如,在某個SMTP的實現中,編解碼器將讀取傳入字節,並將它們解碼為一個自定義的消息類型,如SmtpRequest
[5]。而在接收端,當一個響應被創建時,將會產生一個SmtpResponse
,其將被編碼回字節以便進行傳輸。
表10-5 ByteToMessageCodec
API
方 法 名 稱
描 述
decode(
ChannelHandlerContext ctx,
ByteBuf in,
List<Object>)
只要有字節可以被消費,這個方法就將會被調用。它將入站ByteBuf
轉換為指定的消息格式,並將其轉發給ChannelPipeline
中的下一個ChannelInboundHandler
decodeLast(
ChannelHandlerContext ctx,
ByteBuf in,
List<Object> out)
這個方法的默認實現委託給了decode
方法。它只會在Channel
的狀態變為非活動時被調用一次。它可以被重寫以實現特殊的處理
encode(
ChannelHandlerContext ctx,
I msg,
ByteBuf out)
對於每個將被編碼並寫入出站ByteBuf
的(類型為I
的)消息來說,這個方法都將會被調用
10.4.2 抽像類MessageToMessageCodec
在10.3.1節中,你看到了一個擴展了MessageToMessageEncoder
以將一種消息格式轉換為另外一種消息格式的例子。通過使用MessageToMessageCodec
,我們可以在一個單個的類中實現該轉換的往返過程。MessageToMessageCodec
是一個參數化的類,定義如下:
public abstract class MessageToMessageCodec<INBOUND_IN,OUTBOUND_IN>
表10-6列出了其中重要的方法。
表10-6 MessageToMessageCodec
的方法
方 法 名 稱
描 述
protected abstract decode(
ChannelHandlerContext ctx,
INBOUND_IN msg,
List<Object> out)
這個方法被調用時會被傳入INBOUND_IN
類型的消息。它將把它們解碼為OUTBOUND_IN
類型的消息,這些消息將被轉發給ChannelPipeline
中的下一個Channel- InboundHandler
protected abstract encode(
ChannelHandlerContext ctx,
OUTBOUND_IN msg,
List<Object> out)
對於每個OUTBOUND_IN
類型的消息,這個方法都將會被調用。這些消息將會被編碼為INBOUND_IN
類型的消息,然後被轉發給ChannelPipeline
中的下一個ChannelOutboundHandler
decode
方法是將INBOUND_IN
類型的消息轉換為OUTBOUND_IN
類型的消息,而encode
方法則進行它的逆向操作。將INBOUND_IN
類型的消息看作是通過網絡發送的類型,而將OUTBOUND_IN
類型的消息看作是應用程序所處理的類型,將可能有所裨益[6]。
雖然這個編解碼器可能看起來有點高深,但是它所處理的用例卻是相當常見的:在兩種不同的消息API之間來回轉換數據。當我們不得不和使用遺留或者專有消息格式的API進行互操作時,我們經常會遇到這種模式。
WebSocket協議
下面關於
MessageToMessageCodec
的示例引用了一個新出的WebSocket協議,這個協議能實現Web瀏覽器和服務器之間的全雙向通信。我們將在第12章中詳細地討論Netty對於WebSocket的支持。
代碼清單10-7展示了這樣的對話[7]可能的實現方式。我們的WebSocketConvertHandler
在參數化MessageToMessageCodec
時將使用INBOUND_IN
類型的WebSocketFrame
,以及OUTBOUND_IN
類型的MyWebSocketFrame
,後者是WebSocketConvertHandler
本身的一個靜態嵌套類。
代碼清單10-7 使用MessageToMessageCodec
public class WebSocketConvertHandler extends
MessageToMessageCodec<WebSocketFrame,
WebSocketConvertHandler.MyWebSocketFrame> {
@Override
protected void encode(ChannelHandlerContext ctx, ← -- 將MyWebSocketFrame 編碼為指定的WebSocketFrame子類型
WebSocketConvertHandler.MyWebSocketFrame msg,
List<Object> out) throws Exception {
ByteBuf payload = msg.getData.duplicate.retain;
switch (msg.getType) { ← -- 實例化一個指定子類型的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, WebSocketFrame msg, ← -- 將WebSocketFrame 解碼為MyWebSocketFrame,並設置FrameType
List<Object> out) throws Exception {
ByteBuf payload = msg.content.duplicate.retain;
if (msg instanceof BinaryWebSocketFrame) {
out.add(new MyWebSocketFrame(
MyWebSocketFrame.FrameType.BINARY, payload));
} else
if (msg instanceof CloseWebSocketFrame) {
out.add(new MyWebSocketFrame (
MyWebSocketFrame.FrameType.CLOSE, payload));
} else
if (msg instanceof PingWebSocketFrame) {
out.add(new MyWebSocketFrame (
MyWebSocketFrame.FrameType.PING, payload));
} else
if (msg instanceof PongWebSocketFrame) {
out.add(new MyWebSocketFrame (
MyWebSocketFrame.FrameType.PONG, payload));
} else
if (msg instanceof TextWebSocketFrame) {
out.add(new MyWebSocketFrame (
MyWebSocketFrame.FrameType.TEXT, payload));
} else
if (msg instanceof ContinuationWebSocketFrame) {
out.add(new MyWebSocketFrame (
MyWebSocketFrame.FrameType.CONTINUATION, payload));
} else
{
throw new IllegalStateException(
"Unsupported websocket msg " + msg);
}
}
public static final class MyWebSocketFrame { ← -- 聲明WebSocketConvertHandler所使用的OUTBOUND_IN 類型
public enum FrameType { ← -- 定義擁有被包裝的有效負載的WebSocketFrame的類型
BINARY,
CLOSE,
PING,
PONG,
TEXT,
CONTINUATION
}
private final FrameType type;
private final ByteBuf data;
public MyWebSocketFrame(FrameType type, ByteBuf data) {
this.type = type;
this.data = data;
}
public FrameType getType {
return type;
}
public ByteBuf getData {
return data;
}
}
}
10.4.3 CombinedChannelDuplexHandler類
正如我們前面所提到的,結合一個解碼器和編碼器可能會對可重用性造成影響。但是,有一種方法既能夠避免這種懲罰,又不會犧牲將一個解碼器和一個編碼器作為一個單獨的單元部署所帶來的便利性。CombinedChannelDuplexHandler
提供了這個解決方案,其聲明為:
public class CombinedChannelDuplexHandler
<I extends ChannelInboundHandler,
O extends ChannelOutboundHandler>
這個類充當了ChannelInboundHandler
和ChannelOutboundHandler
(該類的類型參數I
和O
)的容器。通過提供分別繼承了解碼器類和編碼器類的類型,我們可以實現一個編解碼器,而又不必直接擴展抽像的編解碼器類。我們將在下面的示例中說明這一點。
首先,讓我們研究代碼清單10-8中的ByteToCharDecoder
。注意,該實現擴展了ByteTo-MessageDecoder
,因為它要從ByteBuf
中讀取字符。
代碼清單10-8 ByteToCharDecoder
類
public class ByteToCharDecoder extends ByteToMessageDecoder { ← -- 擴展了ByteToMessageDecoder
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in,
List<Object> out) throws Exception {
while (in.readableBytes >= 2) { ← -- 將一個或者多個Character對像添加到傳出的List 中
out.add(in.readChar);
}
}
}
這裡的decode
方法一次將從ByteBuf
中提取2字節,並將它們作為char
寫入到List
中,其將會被自動裝箱為Character
對象。
代碼清單10-9包含了CharToByteEncoder
,它能將Character
轉換回字節。這個類擴展了MessageToByteEncoder
,因為它需要將char
消息編碼到ByteBuf
中。這是通過直接寫入ByteBuf
做到的。
代碼清單10-9 CharToByteEncoder
類
public class CharToByteEncoder extends
MessageToByteEncoder<Character> { ← -- 擴展了MessageToByteEncoder
@Override
public void encode(ChannelHandlerContext ctx, Character msg,
ByteBuf out) throws Exception {
out.writeChar(msg); ← -- 將Character 解碼為char,並將其寫入到出站ByteBuf 中
}
}
既然我們有了解碼器和編碼器,我們將會結合它們來構建一個編解碼器。代碼清單10-10展示了這是如何做到的。
代碼清單10-10 CombinedChannelDuplexHandler<I,O>
public class CombinedByteCharCodec extends
CombinedChannelDuplexHandler<ByteToCharDecoder, CharToByteEncoder> { ← -- 通過該解碼器和編碼器實現參數化CombinedByteCharCodec
public CombinedByteCharCodec {
super(new ByteToCharDecoder, new CharToByteEncoder); ← -- 將委託實例傳遞給父類
}
}
正如你所能看到的,在某些情況下,通過這種方式結合實現相對於使用編解碼器類的方式來說可能更加的簡單也更加的靈活。當然,這可能也歸結於個人的偏好問題。
10.5 小結
在本章中,我們學習了如何使用Netty的編解碼器API來編寫解碼器和編碼器。你也瞭解了為什麼使用這個API相對於直接使用ChannelHandler
API更好。
你看到了抽像的編解碼器類是如何為在一個實現中處理解碼和編碼提供支持的。如果你需要更大的靈活性,或者希望重用現有的實現,那麼你還可以選擇結合他們,而無需擴展任何抽像的編解碼器類。
在下一章中,我們將討論作為Netty框架本身的一部分的ChannelHandler
實現和編解碼器,你可以利用它們來處理特定的協議和任務。
[1] 比如用來產生一個LastHttpContent
消息。——譯者注
[2] 指調用readableBytes
方法。——譯者注
[3] 這裡實際上拋出的是一個Signal
,詳見io.netty.util.Signal
類。——譯者注
[4] 另外一種格式的消息。——譯者注
[5] 位於基於Netty的SMTP/LMTP客戶端項目中(https://github.com/normanmaurer/niosmtp)。——譯者注
[6] 即有助於理解這兩個類型簽名的實際意義。——譯者注
[7] 指Web瀏覽器和服務器之間的雙向通信。——譯者注