序章
前回の記事では、あるメッセージから別のメッセージに変換するためのnettyのフレームワークはMessageToMessageエンコーダーと呼ばれることを説明しました。ただし、メッセージからメッセージへの変換では、処理中のチャネル内のメッセージの変換のみが考慮されますが、チャネルで送信される最終データはByteBufである必要があるため、メッセージとByteBuf間の相互変換のフレームワーク(MessageToByteと呼ばれる)も必要です。
ここでのバイトは、バイトタイプではなくByteBufを参照していることに注意してください。
MessageToByteフレームワークの概要
拡張とユーザーのカスタマイズを容易にするために、nettyはMessageToByteフレームワークのセットをカプセル化します。このフレームワークには、MessageToByteEncoder、ByteToMessageDecoder、ByteToMessageCodecの3つのコアクラスがあります。
これらの3つのコアクラスの定義を見てみましょう。
public abstract class MessageToByteEncoder<I> extends ChannelOutboundHandlerAdapter
public abstract class ByteToMessageDecoder extends ChannelInboundHandlerAdapter
public abstract class ByteToMessageCodec<I> extends ChannelDuplexHandler
これらの3つのクラスは、それぞれChannelOutboundHandlerAdapter、ChannelInboundHandlerAdapter、およびChannelDuplexHandlerを継承します。これらはそれぞれ、チャネルへのメッセージの書き込み、チャネルからのメッセージの読み取り、およびチャネルへのメッセージの読み取りと書き込みの双方向操作を表します。
これらの3つのクラスは抽象クラスです。次に、これら3つのクラスの特定の実装ロジックを詳細に分析します。
MessageToByteEncoder
まずエンコーダーを見てみましょう。MessageToByteEncoderとMessageToMessageEncoderのソースコードの実装を比較すると、多くの類似点があることがわかります。
まず、メッセージタイプマッチング用のTypeParameterMatcherがMessageToByteEncoderで定義されます。
このマッチャーは、受信したメッセージタイプを照合するために使用されます。タイプが一致する場合はメッセージ変換操作が実行され、一致しない場合はメッセージがチャネルに直接書き込まれます。
MessageToMessageEncoderとは異なり、MessageToByteEncoderには追加のpreferDirectフィールドがあり、メッセージがByteBufに変換されるときにdiretBufを使用するかヒープBufを使用するかを示します。
このフィールドの使用法は次のとおりです。
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, @SuppressWarnings("unused") I msg,
boolean preferDirect) throws Exception {
if (preferDirect) {
return ctx.alloc().ioBuffer();
} else {
return ctx.alloc().heapBuffer();
}
}
最後に、コアメソッドの書き込みを見てみましょう。
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ByteBuf buf = null;
try {
if (acceptOutboundMessage(msg)) {
@SuppressWarnings("unchecked")
I cast = (I) msg;
buf = allocateBuffer(ctx, cast, preferDirect);
try {
encode(ctx, cast, buf);
} finally {
ReferenceCountUtil.release(cast);
}
if (buf.isReadable()) {
ctx.write(buf, promise);
} else {
buf.release();
ctx.write(Unpooled.EMPTY_BUFFER, promise);
}
buf = null;
} else {
ctx.write(msg, promise);
}
} catch (EncoderException e) {
throw e;
} catch (Throwable e) {
throw new EncoderException(e);
} finally {
if (buf != null) {
buf.release();
}
}
}
前述のように、writeメソッドは最初にマッチャーを使用して、それが受け入れられるメッセージのタイプであるかどうかを判断します。受け入れられる場合は、encodeメソッドを呼び出して、メッセージオブジェクトをByteBufに変換します。そうでない場合は、直接書き込みを行います。チャネルへのメッセージ。
MessageToMessageEncoderとは異なり、encodeメソッドはCodecOutputListではなくByteBufオブジェクトを渡す必要があります。
MessageToByteEncoderには、次のように実装する必要がある抽象メソッドencodeがあります。
protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception;
ByteToMessageDecoder
ByteToMessageDecoderは、チャネル内のByteBufメッセージを特定のメッセージタイプに変換するために使用されます。デコーダーで最も重要なメソッドは、以下に示すようにchannelReadメソッドです。
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
CodecOutputList out = CodecOutputList.newInstance();
try {
first = cumulation == null;
cumulation = cumulator.cumulate(ctx.alloc(),
first ? Unpooled.EMPTY_BUFFER : cumulation, (ByteBuf) msg);
callDecode(ctx, cumulation, out);
} catch (DecoderException e) {
throw e;
} catch (Exception e) {
throw new DecoderException(e);
} finally {
try {
if (cumulation != null && !cumulation.isReadable()) {
numReads = 0;
cumulation.release();
cumulation = null;
} else if (++numReads >= discardAfterReads) {
numReads = 0;
discardSomeReadBytes();
}
int size = out.size();
firedChannelRead |= out.insertSinceRecycled();
fireChannelRead(ctx, out, size);
} finally {
out.recycle();
}
}
} else {
ctx.fireChannelRead(msg);
}
}
channelReadは、読み取るObjectオブジェクトを受け取ります。ここではByteBufメッセージのみが受け入れられるためmsg instanceof ByteBuf
、メッセージのタイプを判別するためにメソッド内で呼び出されます。ByteBufタイプのメッセージでない場合、メッセージは変換されません。
出力オブジェクトはCodecOutputListです。ByteBufをCodecOutputListに変換した後、fireChannelReadメソッドを呼び出してoutオブジェクトを渡します。
ここで重要なのは、受信したByteBufをCodecOutputListに変換する方法です。
変換メソッドはcallDecodeと呼ばれ、accumulationと呼ばれるパラメーターを受け取ります。上記のメソッドでは、accumulationと呼ばれるaccumulationと非常によく似た名前も表示されます。では、この2つの違いは何ですか?
ByteToMessageDecoderでは、accumulationはByteBufオブジェクトであり、Cumulatorはaccumulateメソッドを定義するインターフェイスです。
public interface Cumulator {
ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in);
}
Cumulatorは、着信ByteBufsを新しいByteBufにマージするために使用されます。
ByteToMessageDecoderには、MERGE_CUMULATORとCOMPOSITE_CUMULATORの2種類の累計が定義されています。
MERGE_CUMULATORは、メモリコピーを使用して、着信ByteBufをターゲットByteBuf累積にコピーします。
また、COMPOSITE_CUMULATORは、ByteBufをCompositeByteBuf構造体に追加するものであり、ターゲットの構造体がより複雑であるため、メモリコピーを実行しません。そのため、速度はダイレクトメモリコピーよりも遅くなります。
ユーザーが拡張したいメソッドは、ByteBufを他のオブジェクトに変換するために使用されるdecodeメソッドです。
protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception;
ByteToMessageCodec
導入される最後のクラスはByteToMessageCodecです。ByteToMessageCodecはメッセージとByteBufの間の相互変換を表し、その中のエンコーダーとデコーダーは上記のMessageToByteEncoderとByteToMessageDecoderです。
ユーザーはByteToMessageCodecを継承して、エンコードとデコードの機能を同時に実現できるため、エンコードとデコードの2つの方法を実装する必要があります。
protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception;
protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception;
ByteToMessageCodecの本質は、MessageToByteEncoderとByteToMessageDecoderをカプセル化し、エンコードとデコードの機能を実装することです。
要約する
ByteBufとユーザー定義メッセージの間で直接変換を実現したい場合は、nettyが提供する上記の3つのエンコーダーを選択することをお勧めします。
この記事はhttp://www.flydean.com/14-0-2-netty-codec-msg-to-bytebuf/に含まれています
最も人気のある解釈、最も深遠な乾物、最も簡潔なチュートリアル、そしてあなたが知らない多くのトリックがあなたが発見するのを待っています!
私の公式アカウントに注意を払うことを歓迎します:「それらをプログラムする」、テクノロジーを理解し、あなたをよりよく理解する!