前言
回顧Netty系列文章:
- Netty 概述(一)
- Netty 架構設計(二)
- Netty Channel 概述(三)
- Netty ChannelHandler(四)
- ChannelPipeline源碼分析(五)
- 字節(jié)緩沖區(qū) ByteBuf (六)(上)
- 字節(jié)緩沖區(qū) ByteBuf(七)(下)
- Netty 如何實現(xiàn)零拷貝(八)
- Netty 程序引導類(九)
- Reactor 模型(十)
- 工作原理詳解(十一)
編碼和解碼:數(shù)據(jù)從一種特定協(xié)議格式到另一種格式的轉換。
處理編碼和解碼的程序通常被稱為編碼器和解碼器。Netty 提供了一些組件,利用它們可以很容易地為各種不同協(xié)議編寫編解碼器。
一、編解碼概述
編解碼其實可以分為兩塊,即編碼和解碼。要知道,在網(wǎng)絡中數(shù)據(jù)都是以字節(jié)碼的形式來傳輸?shù)?,而我們只能識別文本、圖片這些格式,因此編寫網(wǎng)絡應用程序不可避免地需要操作字節(jié),將我們能夠識別的數(shù)據(jù)轉換成網(wǎng)絡能夠識別的程序,這個過程稱之為編解碼。
1.1、編解碼器概述
編碼也稱為序列化,它將對象序列化為字節(jié)數(shù)組,用于網(wǎng)絡傳輸、數(shù)據(jù)持久化或者其他用途。
解碼稱為反序列化,它把從網(wǎng)絡、磁盤等讀取的字節(jié)數(shù)組還原成原始對象(通常是原始對象的拷貝),以方便后續(xù)的業(yè)務邏輯操作。
實現(xiàn)編解碼功能的程序也被稱為編解碼器,編解碼器的作用就是將原始字節(jié)數(shù)組與目標程序數(shù)據(jù)格式進行互轉。
編解碼器由兩部分組成:解碼器(decoder)和編碼器(encoder)。
大家可以想象發(fā)送消息的這個過程。消息是一個結構化的應用程序中的數(shù)據(jù)。編碼器轉換消息格式為適合傳輸?shù)臄?shù)據(jù)格式,而相應的解碼器是將傳輸數(shù)據(jù)轉換回程序中的消息格式。邏輯上來說,轉換消息格式為適合傳輸?shù)臄?shù)據(jù)格式是當作操作出站(outbound)數(shù)據(jù),而將傳輸數(shù)據(jù)轉換回程序中的消息格式是處理入站(inbound)數(shù)據(jù)。
1.2、Netty 內(nèi)嵌的編碼器
Netty 內(nèi)嵌了眾多的編解碼器來簡化開發(fā)。下圖展示了Netty 的內(nèi)嵌編解碼器。
可以看出,Netty 的內(nèi)嵌編解碼器基本上囊括了網(wǎng)絡編程中可能需要涉及的編解碼工作,包括以下內(nèi)容:
- 支持字節(jié)與消息的轉換、Base64的轉換、解壓縮文件。
- 對HTTP、HTTP2、DNS、SMTP、STOMP、MQTT、Socks等協(xié)議的支持。
- -對XML、JSON 、Redis、 Memcached、Protobuf等流行格式的支持。
編碼器和解碼器的結構很簡單,消息被編碼、解碼后自動通過 ReferenceCountUtil.release(message)釋放。如果不想釋放消息可以使用ReferenceCountUtil.retain(message),主要區(qū)別是retain會使引用數(shù)量增加而不會發(fā)生消息,大多數(shù)時候不需要這么做。
二、解碼器
解碼器的主要職責是負責將入站數(shù)據(jù)從一種格式轉換到另一種格式。Netty 提供了豐富的解碼器抽象基類。方便開發(fā)者自定義解碼器。
這些基類主要分為以下兩類:
- 解碼從字節(jié)到消息(ByteToMessageDecoder 和 ReplayingDecoder)。
- 解碼從消息到消息(MessageToMessageDecoder)。
Netty 的解碼器是ChannelInboundHandler的抽象實現(xiàn)。在實際應用中使用解碼器很簡單,就是將入站數(shù)據(jù)轉換格式后傳遞到ChannelPipeline中的下一個ChannelInboundHandler進行處理。將解碼器放在ChannelPipeline中,會使整個程序變得靈活,同時也能方便重用邏輯。
2.1、ByteToMessageDecoder 抽象類
ByteToMessageDecoder 抽象類用于將字節(jié)轉為消息(或其他字節(jié)序列)。ByteToMessageDecoder 繼承自ChannelInboundHandlerAdapter。ChannelInboundHandlerAdapter以類似流的方法將字節(jié)從ByteBuf解碼為另一種消息類型。
2.1.1、常用方法
在處理網(wǎng)絡數(shù)據(jù)時,有時數(shù)據(jù)比較大,不能一次性發(fā)送完畢,會分配發(fā)送。那么又如何獲知數(shù)據(jù)已經(jīng)發(fā)送完畢了呢?這個ByteToMessageDecoder抽象類會緩存入站的數(shù)據(jù),并提供了以下幾個方法,方便開發(fā)者使用。
這些方法的核心源碼如下:
protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception;
protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.isReadable()) {
// Only call decode() if there is something left in the buffer to decode.
// See https://github.com/netty/netty/issues/4386
decodeRemovalReentryProtection(ctx, in, out);
}
}
final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
throws Exception {
decodeState = STATE_CALLING_CHILD_DECODE;
try {
decode(ctx, in, out);
} finally {
boolean removePending = decodeState == STATE_HANDLER_REMOVED_PENDING;
decodeState = STATE_INIT;
if (removePending) {
fireChannelRead(ctx, out, out.size());
out.clear();
handlerRemoved(ctx);
}
}
}
對上述方法說明如下:
- decode():這是必須要實現(xiàn)的唯一抽象方法。decode()方法被調用時將會傳入一個包含了傳入數(shù)據(jù)的ByteBuf,以及一個用來添加解碼消息的List。對這個方法的調用將會重復執(zhí)行,直到確定沒有新的元素被添加到該List,或者該ByteBuf中沒有更多可讀的字節(jié)時為止。然后,如果List不為空,那么它的內(nèi)容將會被傳遞給ChannelPipeline中的下一個ChannelInboundHandler。
- decodeLast():Netty 提供的這個默認實現(xiàn)只是簡單地調用了decode()方法。當Channel的狀態(tài)變?yōu)榉腔顫姇r,這個方法會被調用一次??梢灾貙懺摲椒ㄒ蕴峁┨厥獾奶幚?。
2.1.2、將字節(jié)轉為整形的解碼器示例
該示例中,每次從入站的ByteBuf讀取4個字節(jié),解碼成整形,并添加到一個List中。當不能再添加數(shù)據(jù)的List時,它所包含的內(nèi)容就會被發(fā)送到下一個ChannelInboundHandler。
public class ToIntegerDecoder extends ByteToMessageDecoder {
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() >= 4) {
out.add(in.readInt());
} }
}
在上述代碼中,步驟如下:
- 實現(xiàn)了繼承ByteToMessageDecoder,用于將字節(jié)解碼為消息。
- 檢查可讀的字節(jié)是否至少有 4 個(一個 int 是4個字節(jié)長度)。
- 從入站的ByteBuf讀取 int ,添加到解碼消息的List中。
整個例子的處理流程如下圖所示:
對于編碼器和解碼器來說,整個過程非常簡單。一旦一個消息被編碼或者解碼,它自動被ReferenceCountUtil.release(message)調用。如果不想釋放消息可以使用ReferenceCountUtil.retain(message)。
三、ReplayingDecoder 抽象類
ReplayingDecoder抽象類是ByteToMessageDecoder的一個子類,ByteToMessageDecoder解碼讀取緩沖區(qū)的數(shù)據(jù)之前需要檢查緩沖區(qū)是否有足夠的字節(jié),使用ReplayingDecoder就無需自己檢查;若ByteBuf中有足夠的字節(jié),則會正常讀?。蝗魶]有足夠的字節(jié)則會停止解碼。
也正因為這樣的包裝使得ReplayingDecode帶有一定的局限性。
- 不是所有的標準ByteBuf操作都被支持,如果調用一個不支持的操作會拋出UnReplayableOperationException。
- 性能上,使用ReplayingDecode要略慢與ByteToMessageDecoder。
如果你能忍受上面列出的限制,相比ByteToMessageDecoder,你可能更喜歡ReplayingDecoder。在滿足需求的情況下推薦使用ByteToMessageDecoder,因為它的處理比較簡單,沒有# ReplayingDecoder實現(xiàn)的那么復雜。
下面代碼是ReplayingDecoder的實現(xiàn):
/**
* Integer解碼器,ReplayingDecoder實現(xiàn)
*/
public class ToIntegerReplayingDecoder extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
out.add(in.readInt());
}
}
四、MessageToMessageDecoder 抽象類
MessageToMessageDecoder 抽象類用于從一種消息解碼為另外一種消息。
核心源碼如下:
public abstract class MessageToMessageDecoder<I> extends ChannelInboundHandlerAdapter {
private final TypeParameterMatcher matcher;
protected MessageToMessageDecoder() {
matcher = TypeParameterMatcher.find(this, MessageToMessageDecoder.class, "I");
}
protected MessageToMessageDecoder(Class<? extends I> inboundMessageType) {
matcher = TypeParameterMatcher.get(inboundMessageType);
}
public boolean acceptInboundMessage(Object msg) throws Exception {
return matcher.match(msg);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
CodecOutputList out = CodecOutputList.newInstance();
try {
if (acceptInboundMessage(msg)) {
@SuppressWarnings("unchecked")
I cast = (I) msg;
try {
decode(ctx, cast, out);
} finally {
ReferenceCountUtil.release(cast);
}
} else {
out.add(msg);
}
} catch (DecoderException e) {
throw e;
} catch (Exception e) {
throw new DecoderException(e);
} finally {
try {
int size = out.size();
for (int i = 0; i < size; i++) {
ctx.fireChannelRead(out.getUnsafe(i));
}
} finally {
out.recycle();
}
}
}
protected abstract void decode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;
}
MessageToMessageDecoder 的decode是需要實現(xiàn)的唯一抽象方法。每個入站消息都被解碼為另外一種格式,然后將解碼后的消息傳遞給管道中的下一個ChannelInboundHandler。
以下是一個MessageToMessageDecoder 的使用示例:
public class IntegerToStringDecoder extends MessageToMessageDecoder<Integer> {
@Override
public void decode(ChannelHandlerContext ctx, Integer msg List<Object> out) throws Exception {
out.add(String.valueOf(msg));
}
}
上述代碼中,IntegerToStringDecoder繼承自MessageToMessageDecoder,用于將 Integer 轉為 String。分為兩步:
- IntegerToStringDecoder繼承自MessageToMessageDecoder。
- 通過tring.valueOf()轉換 Integer 消息的字符串。
入站消息是按照在類定義中聲明的參數(shù)(這里是Integer)而不是ByteBuf來解析的。在例子中,解碼消息(這里是String)將被添加到List<Object>,并傳遞到下一個ChannelInboundHandler。
整個例子的處理流程圖如下:文章來源:http://www.zghlxwxcb.cn/news/detail-467234.html
總結
上述我們重點講解了 Netty 中的解碼器相關知識。下節(jié)我們就來講解一下編碼器。文章來源地址http://www.zghlxwxcb.cn/news/detail-467234.html
到了這里,關于【Netty】Netty 解碼器(十二)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!