Nettyシリーズ:カスタムエンコードとデコーダで注意すべき問題

6273 ワード

概要


先のシリーズでは、nettyのchannelがByteBufタイプのオブジェクトのみを受け入れることについて言及しましたが、ByteBufオブジェクトでなければ符号化とデコーダで変換する必要があるので、今日はnettyカスタムの符号化とデコーダ実装で注意すべき問題についてお話しします.

カスタムエンコーダとデコーダの実装


nettyが持参したエンコーダとデコーダを紹介する前に、カスタムエンコーダとデコーダの実現方法を教えてあげましょう.
Nettyのすべてのエンコーダおよびデコーダは、ChannelInboundHandlerAdapterおよびChannelOutboundHandlerAdapterから派生している.
ChannelOutboundHandlerAdapterにとって最も重要な2つのクラスはMessageToByteEncoderとMessageToMessageEncoderです.
MessageToByteEncoderは、メッセージをByteBufに符号化するものであり、このクラスもカスタマイズ符号化で最も一般的なクラスであり、このクラスを直接継承し、encodeメソッドを実現すればよい.このクラスには、メッセージのオブジェクトタイプを指定する汎用型があることに注意してください.
たとえばIntegerをByteBufに変換したい場合は、次のように書くことができます.
       public class IntegerEncoder extends MessageToByteEncoder {
            @Override
           public void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out)
                   throws Exception {
               out.writeInt(msg);
           }
       }

MessageToMessageEncoderはメッセージとメッセージの間で変換されます.メッセージは直接channelに書き込まれないため、MessageToByteEncoderと連携して使用する必要があります.
次にIntegerからStringの例を示します.
       public class IntegerToStringEncoder extends
               MessageToMessageEncoder {
  
            @Override
           public void encode(ChannelHandlerContext ctx, Integer message, List out)
                   throws Exception {
               out.add(message.toString());
           }
       }

ChannelInboundHandlerAdapterにとって最も重要な2つのクラスはByteToMesageDecoderとMessageToMessageDecoderです.
ByteToMessageDecoderは、ByteBufを対応するメッセージ・タイプに変換するもので、このクラスを継承してdecodeメソッドを実装する必要があります.次に、ByteBufからすべての読み取り可能なバイトを読み出し、結果を新しいByteBufに配置します.
       public class SquareDecoder extends ByteToMessageDecoder {
            @Override
           public void decode(ChannelHandlerContext ctx, ByteBuf in, List out)
                   throws Exception {
               out.add(in.readBytes(in.readableBytes()));
           }
       }
   

MessageToMessageDecoderはメッセージとメッセージの間の変換であり、同様にdecodeメソッドを実装するだけでよい.以下のようにStringからIntegerに変換する.
       public class StringToIntegerDecoder extends
               MessageToMessageDecoder {
  
            @Override
           public void decode(ChannelHandlerContext ctx, String message,
                              List out) throws Exception {
               out.add(message.length());
           }
       }

ReplayingDecoder


上のコードは簡単に見えますが、実装中にいくつかの問題に注意してください.
Decoderの場合、ByteBufからデータを読み込み、変換します.しかし、読み出し中にByteBufにおけるデータの変動がわからず、読み出し中にByteBufが準備されていない可能性があるため、読み出し時にByteBufにおける読み取り可能バイトの大きさを判断する必要がある.
例えば、データ構造を解析する必要があります.このデータ構造の最初の4バイトはintで、後のbyte配列の長さを表します.ByteBufに4バイトあるかどうかを判断してから、この4バイトをByte配列の長さとして読み出し、その後、この長さのByte配列を読み出して、最終的に読み出す結果を得る必要があります.もしその中のあるステップに問題が発生したり、読み取り可能なバイトの長さが足りなかったりしたら、では、直接戻って、次の読み取りを待つ必要があります.次のようになります.
   public class IntegerHeaderFrameDecoder extends ByteToMessageDecoder {
  
      @Override
     protected void decode(ChannelHandlerContext ctx,
                             ByteBuf buf, List out) throws Exception {
  
       if (buf.readableBytes() < 4) {
          return;
       }
  
       buf.markReaderIndex();
       int length = buf.readInt();
  
       if (buf.readableBytes() < length) {
          buf.resetReaderIndex();
          return;
       }
  
       out.add(buf.readBytes(length));
     }
   }

このような判断は複雑であり、エラーも発生する可能性があります.この問題を解決するために、nettyは上記の操作を簡略化するためにReplayingDecoderを提供しています.ReplayingDecoderでは、すべてのByteBufが準備された状態にあると仮定し、直接中間から読み取るだけでいいです.
上記の例は、次のようにReplayingDecoderで書き換えられています.
   public class IntegerHeaderFrameDecoder
        extends ReplayingDecoder {
  
     protected void decode(ChannelHandlerContext ctx,
                             ByteBuf buf, List out) throws Exception {
  
       out.add(buf.readBytes(buf.readInt()));
     }
   }

その実現原理は,対応するバイト情報の読み取りを試みることであり,読み取らなければ例外を投げ出し,ReplayingDecoderが異常を受信するとdecodeメソッドが再呼び出される.
ReplayingDecoderは非常に簡単に使用できますが、2つの問題があります.
1つ目の問題は、decodeメソッドを繰り返し呼び出すため、ByteBuf自体が変化しない場合、decodeが同じByteBufを繰り返し、パフォーマンスの浪費を招くパフォーマンスの問題です.この問題を解決するには、decodeの過程で段階的に行われます.例えば、上記の例では、Byte配列の長さを読み出してから、本当のbyte配列を読み出す必要があります.したがってbyte配列の長さの和を読み終わったらcheckpoint()メソッドを呼び出してセーブポイントを作成し、次にdecodeメソッドを実行するときにこのセーブポイントをスキップして、次のように後続の実行プロセスを継続することができます.
   public enum MyDecoderState {
     READ_LENGTH,
     READ_CONTENT;
   }
  
   public class IntegerHeaderFrameDecoder
        extends ReplayingDecoder {
  
     private int length;
  
     public IntegerHeaderFrameDecoder() {
       // Set the initial state.
       super(MyDecoderState.READ_LENGTH);
     }
  
      @Override
     protected void decode(ChannelHandlerContext ctx,
                             ByteBuf buf, List out) throws Exception {
       switch (state()) {
       case READ_LENGTH:
         length = buf.readInt();
         checkpoint(MyDecoderState.READ_CONTENT);
       case READ_CONTENT:
         ByteBuf frame = buf.readBytes(length);
         checkpoint(MyDecoderState.READ_LENGTH);
         out.add(frame);
         break;
       default:
         throw new Error("Shouldn't reach here.");
       }
     }
   }

2つ目の問題は、同じインスタンスのdecodeメソッドが複数回呼び出される可能性があることです.ReplayingDecoderにプライベート変数がある場合は、複数回の呼び出しによるデータ汚染を回避するために、このプライベート変数の洗浄作業を考慮する必要があります.

まとめ


上記のいくつかのクラスを継承することで,符号化と復号化の論理を自分で実現することができる.しかし、カスタムコードとデコーダは複雑すぎるのではないでしょうか.また、読み出すbyte配列の大きさを判断する必要があります.もっと簡単な方法はありませんか?
はい、nettyシリーズの次の文章を楽しみにしてください:nettyが持っているエンコーダとデコーダ.
本明細書の例は、learn-netty4を参照することができる.
この文書はhttp://www.flydean.com/14-netty-cust-codec/に収録されています.
最も通俗的な解読、最も深い乾物、最も簡潔なチュートリアル、多くのあなたが知らない小さなテクニックなどを発見します!
私の公衆番号に注目することを歓迎します:“プログラムのあれらの事”、技術を理解して、更にあなたを理解します!