Nettyシリーズ之:カスタム符号化デコーダ

5517 ワード

概要
先のnettyシリーズでは,オブジェクトまたはStringをByteBufに変換する方法について述べ,nettyが持参したencoderとdecoderを用いることで非常に便利なオブジェクトとByteBuf間の変換を実現し,channelにオブジェクトと文字列を自由に書き込むことができるようになった.
nettyが持参したエンコーダを使用するのはもちろんいいですが、符号化中にデータを変換したり、オブジェクトのフィールドを選択したりするなど、特別なニーズがある場合は、カスタム符号化デコーダが必要になる可能性があります.
カスタムエンコーダ
カスタムエンコーダは、MessageToByteEncoderクラスを継承し、encodeメソッドを実装し、このメソッドに特定の符号化ロジックを書き込む必要があります.
この例では2のN次方を計算したいと考えていますが、1枚の紙を100回折り畳むと地球から月までの高さに達すると言われています.このような大きなデータは普通のnumberでは入れられないに違いありません.BigIntegerを使ってこの巨大な数字を保存します.
このBigIntegerをエンコーダでbyte配列に変換する必要がある.同時にbyte配列の読み出しの過程で、どのbyteデータが同じBigIntegerに属しているのかを定義する必要があります.これは、書き込まれたデータフォーマットを約束する必要があります.
ここでは3つの部分のデータ構造を用いてBigIntegerを表す.第1部はmagic word、すなわち魔法語で、ここでは魔法語「N」を使用し、この魔法語を読むと次の数字がBigIntegerであることを示します.第2部はbigInteger数を表すbyte配列の長さであり、この長さ値を取得するとすべてのbyte配列値が読み込まれ、最後にBigIntegerに変換される.
BigIntegerはNumberのサブクラスであるため、エンコーダをより汎用化するために、MessageToByteEncoderの汎用としてNumberを使用し、コア符号化コードは以下の通りである.
 protected void encode(ChannelHandlerContext ctx, Number msg, ByteBuf out) {
        //  number    ByteBuf
        BigInteger v;
        if (msg instanceof BigInteger) {
            v = (BigInteger) msg;
        } else {
            v = new BigInteger(String.valueOf(msg));
        }

        //  BigInteger    byte[]  
        byte[] data = v.toByteArray();
        int dataLength = data.length;

        //  Number    
        out.writeByte((byte) 'N'); //    
        out.writeInt(dataLength);  //     
        out.writeBytes(data);      //      
    }

カスタムデコーダ
符号化後のbyte配列があれば,デコーダで復号できる.
前節では,符号化されたデータフォーマットは魔法語N+配列長+真のデータである.
魔法語の長さは1バイト、配列の長さは4バイトで、前の部分は全部で5バイトです.したがって、復号化の際には、まず、ByteBufにおける可読バイトの長さが5未満であるか否かを判断し、5未満であればデータが無効であることを示し、直接returnすることができる.
可読バイトの長さが5より大きい場合は、データが有効であることを示し、データの復号を行うことができる.
復号化の過程で注意しなければならないのは、すべてのデータが私たちが望んでいるフォーマットではないことであり、読み出しの過程で私たちが知らないフォーマットを読んだ場合、このデータが私たちが望んでいるものではないことを示していれば、他のhandlerに渡して処理することができます.
しかし、ByteBufでは、readメソッドを呼び出すとreader indexが位置を移動するため、本格的なデータの読み取りの前にByteBufのmarkReaderIndexメソッドを呼び出してreaderIndexを記録する必要がある.その後、魔法語、配列長、残りのデータをそれぞれ読み出し、次のようにBigIntegerに変換します.
 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) {
        //             
        if (in.readableBytes() < 5) {
            return;
        }
        in.markReaderIndex();
        //      
        int magicNumber = in.readUnsignedByte();
        if (magicNumber != 'N') {
            in.resetReaderIndex();
            throw new CorruptedFrameException("      : " + magicNumber);
        }
        //        
        int dataLength = in.readInt();
        if (in.readableBytes() < dataLength) {
            in.resetReaderIndex();
            return;
        }
        //           BigInteger
        byte[] decoded = new byte[dataLength];
        in.readBytes(decoded);
        out.add(new BigInteger(decoded));
    }

pipelineにエンコードデコーダを追加
2つの符号化デコーダがあり、pipelineに追加して呼び出す必要があります.
ChannelInitializerを実装するinitChannelでは、ChannelPipelineを初期化することができ、この例の初期化コードは以下の通りである.
//       
        pipeline.addLast(ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP));
        pipeline.addLast(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP));

        //   number     
        pipeline.addLast(new NumberDecoder());
        pipeline.addLast(new NumberEncoder());

        //         
        pipeline.addLast(new CustomProtocolServerHandler());

最後の行は真のビジネス処理ロジックであり、NumberDecoderおよびNumberEncoderは符号化およびデコーダである.ここではまた,GZIPを用いた対流データの圧縮にZlibEncoderを用いた.
圧縮の利点は、データ転送の数を減らし、転送効率を向上させることです.その本質も符号化デコーダである.
計算2のN次方
計算2のN次側の論理は、まずクライアントが2をサーバ側に送信し、サーバ側がそのメッセージを受信して結果1に乗算し、結果をクライアントに書き戻し、クライアントがメッセージを受信した後に2をサーバ側に送信し、サーバ側は前回の計算結果に2を乗じてクライアントに送信し、このようにN回実行するまでプッシュする.
まず、クライアントの送信ロジックを見てみましょう.
//     2 1000  
        ChannelFuture future = null;
        for (int i = 0; i < 1000 && next <= CustomProtocolClient.COUNT; i++) {
            future = ctx.write(2);
            next++;
        }

nextが計算するCOUNT以下である場合、channelに2を書き込む.
サーバの場合、channelRead 0メソッドでは、メッセージを読み取り、結果に乗算してクライアントに結果を書き返します.
    public void channelRead0(ChannelHandlerContext ctx, BigInteger msg) throws Exception {
        //      msg  2,        
        count++;
        result = result.multiply(msg);
        ctx.writeAndFlush(result);
    }

クライアントは読み出したメッセージの数を統計し、メッセージの数=COUNTで計算が完了した場合、結果を後続の使用のために保存することができ、そのコアコードは以下の通りである.
    public void channelRead0(ChannelHandlerContext ctx, final BigInteger msg) {
        receivedMessages ++;
        if (receivedMessages == CustomProtocolClient.COUNT) {
            //     ,     answer 
            ctx.channel().close().addListener(future -> {
                boolean offered = answer.offer(msg);
                assert offered;
            });
        }
    }

まとめ
本稿では,Numberの符号化デコーダを実現し,実際には任意のオブジェクトを実装する符号化デコーダをカスタマイズすることができる.
本明細書の例は、learn-netty4を参照することができる.
この文書はhttp://www.flydean.com/13-netty-customprotocol/に収録されています.
最も通俗的な解読、最も深い乾物、最も簡潔なチュートリアル、多くのあなたが知らない小さなテクニックなどを発見します!
私の公衆番号に注目することを歓迎します:“プログラムのあれらの事”、技術を理解して、更にあなたを理解します!