Netty コーデック、Netty カスタム コーデックによるスティッキー パケット アンパックの問題の解決、Netty コーデックの実行プロセスの詳細な説明

1. コーデックの概要

1. コーデックの概要

Netty がメッセージを送受信すると、データ変換が発生します。受信メッセージはデコードされ (バイトから Java オブジェクトなどの別の形式に変換され)、送信メッセージはバイトにエンコードされます。

Netty は一連の実用的なコーデックを提供しており、そのすべてが ChannelInboundHadnler または ChannelOutboundHandler インターフェイスを実装しています。これらのクラスでは、channelRead メソッドがオーバーライドされています。受信を例にとると、このメソッドは受信チャネルから読み取られるすべてのメッセージに対して呼び出されます。その後、デコーダによって提供される decode() メソッドを呼び出してデコードし、デコードされたバイトを ChannelPipeline 内の次の ChannelInboundHandler に転送します。

2. エンコーダクラス関係図

エンコーダーはすべて MessageToByteEncoder 抽象クラスを継承しており、その encode メソッドを書き直す必要があります。
ここに画像の説明を挿入
エンコーダーが ChannelOutboundHandlerAdapter を継承し、アウトバウンド操作のみが実行されることを示していることがわかりました。

3. デコーダクラス関係図

デコーダーはすべて ByteToMessageDecoder 抽象クラスを継承するため、その decode メソッドを書き直す必要があります。
ここに画像の説明を挿入
デコーダーが ChannelInboundHandlerAdapter を継承していることがわかりました。これは、受信操作が実行されることを意味します。

2. コーデックを例として受信と送信を理解します。

1.サーバー側


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class MyServer {
    
    
    public static void main(String[] args) throws Exception{
    
    

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
    
    

            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
    
    
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
    
    
                            ChannelPipeline pipeline = ch.pipeline();//一会下断点

                            //入站的handler进行解码 MyByteToLongDecoder
                            pipeline.addLast(new MyByteToLongDecoder());
                            //出站的handler进行编码
                            pipeline.addLast(new MyLongToByteEncoder());
                            //自定义的handler 处理业务逻辑
                            pipeline.addLast(new MyServerHandler());
                            System.out.println("end");
                        }
            }); //自定义初始化pipeline

            ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
            channelFuture.channel().closeFuture().sync();

        }finally {
    
    
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}



import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class MyServerHandler extends SimpleChannelInboundHandler<Long> {
    
    
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
    
    

        System.out.println("从客户端" + ctx.channel().remoteAddress() + " 读取到long " + msg);
        //给客户端发送一个long
        ctx.writeAndFlush(98765L);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    
    
        cause.printStackTrace();
        ctx.close();
    }
}

2. クライアント側


import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class MyClient {
    
    
    public static void main(String[] args)  throws  Exception{
    
    

        EventLoopGroup group = new NioEventLoopGroup();

        try {
    
    

            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group).channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
    
    
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
    
    

                            ChannelPipeline pipeline = ch.pipeline();

                            //加入一个出站的handler 对数据进行一个编码
                            pipeline.addLast(new MyLongToByteEncoder());

                            //这时一个入站的解码器(入站handler )
                            pipeline.addLast(new MyByteToLongDecoder());
                            //加入一个自定义的handler , 处理业务
                            pipeline.addLast(new MyClientHandler());

                        }
            }); //自定义一个初始化类

            ChannelFuture channelFuture = bootstrap.connect("localhost", 7000).sync();

            channelFuture.channel().closeFuture().sync();

        }finally {
    
    
            group.shutdownGracefully();
        }
    }
}


import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class MyClientHandler  extends SimpleChannelInboundHandler<Long> {
    
    
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
    
    

        System.out.println("服务器的ip=" + ctx.channel().remoteAddress());
        System.out.println("收到服务器消息=" + msg);

    }

    //重写channelActive 发送数据
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
        System.out.println("MyClientHandler 发送数据");
        ctx.writeAndFlush(123456L); //发送的是一个long
    }
}

3. コーデック


import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

public class MyLongToByteEncoder extends MessageToByteEncoder<Long> {
    
    
    //编码方法
    @Override
    protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
    
    

        System.out.println("MyLongToByteEncoder encode 被调用");
        System.out.println("msg=" + msg);
        out.writeLong(msg);

    }
}


import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

public class MyByteToLongDecoder extends ByteToMessageDecoder {
    
    
    /**
     *
     * decode方法 会根据接收的数据,被调用多次, 直到确定没有新的元素被添加到list
     * , 或者是ByteBuf 没有更多的可读字节为止
     * 如果list out 不为空,就会将list的内容传递给下一个 channelinboundhandler处理, 该处理器的方法也会被调用多次
     *
     * @param ctx 上下文对象
     * @param in 入站的 ByteBuf
     * @param out List 集合,将解码后的数据传给下一个handler
     * @throws Exception
     */
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    
    

        System.out.println("MyByteToLongDecoder 被调用");
        //因为 long 8个字节, 需要判断有8个字节,才能读取一个long
        if(in.readableBytes() >= 8) {
    
    
            out.add(in.readLong());
        }
    }
}

3. 実行して結果を表示する

クライアント側:

MyClientHandler がデータを送信します
MyLongToByteEncoder のエンコードは
msg=123456と呼ばれます
MyByteToLongDecoder は
サーバーと呼ばれます ip=localhost/127.0.0.1:7000
受信したサーバー message=98765

サーバ側:


client/127.0.0.1:58478 から long 123456 を読み取るために MyByteToLongDecoder が呼び出さ
れました。 MyLongToByteEncoder エンコードが
msg=98765で呼び出されました。

実行結果によれば、実行プロセスはおおよそ次のとおりであると単純に結論付けることができます。

ここに画像の説明を挿入

4. 注意すべき事項

コーデックは指定されたタイプのデータのみを扱うことができ、データタイプが一致しない場合、コーデックはスキップされますが、データは失われません。

たとえば、クライアント ctx.writeAndFlush(Unpooled.copiedBuffer(“abcdabcdabcdabcd”, CharsetUtil.UTF_8)); が 16 ビット文字列を送信した場合、Encoder の encode メソッドは実行されますが、エンコードされません。したがって、Encoder を作成するときは、受信データ型と処理されるデータ型の一貫性に注意を払う必要があります。

MessageToByteEncoder の書き込みメソッドを見てみましょう。

// io.netty.handler.codec.MessageToByteEncoder#write
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
    
    
    ByteBuf buf = null;
    try {
    
    
        if (acceptOutboundMessage(msg)) {
    
    //判断当前msg 是不是应该处理的类型,如果是就处理,不是就跳过encode
            @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();
        }
    }
}

3. Nettyのその他の内蔵コーデック

Netty には多くのコーデックが含まれており、通常は日常の作業には十分です。

1、再生デコーダ

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;

import java.util.List;

public class MyByteToLongDecoder extends ReplayingDecoder<Void> {
    
    
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    
    

        System.out.println("MyByteToLongDecoder2 被调用");
        //在 ReplayingDecoder 不需要判断数据是否足够读取,内部会进行处理判断
        out.add(in.readLong());
    }
}

ReplayingDecoder は使いやすいですが、いくつかの制限もあります:
1. すべての ByteBuf 操作がサポートされているわけではなく、サポートされていないメソッドが呼び出された場合は、UnsupportedOperationException がスローされます。
2. ReplayingDecoder は、場合によっては ByteToMessageDecoder よりも若干遅くなる場合があります。たとえば、ネットワークが遅く、メッセージ形式が複雑な場合、メッセージが複数のフラグメントに分割され、速度が遅くなります。

2. その他のエンコーダ

LineBasedFrameDecoder: このクラスは Netty 内でも使用され、データを解析するための区切り文字として行末制御文字 (\n または \r\n) を使用します。
DelimiterBasedFrameDecoder: カスタム特殊文字をメッセージ区切り文字として使用します。
HttpObiectDecoder: HTTP データのデコーダー。
LengthFieldBasedFrameDecoder: 長さを指定してフル パケット メッセージを識別し、スティッキー パケットとハーフパケット メッセージを自動的に処理できるようにします。

エンコーダとデコーダはペアで提供されます。

3. 内蔵コーデックにより、貼り付けと解凍の問題が解決されます。

スティッキー問題と解凍問題を解決するための Netty の 4 つのソリューション

4. コーデックをカスタマイズして、貼り付けと解凍の問題を実現します。

Netty はスティッキー パケットのアンパックの問題を解決し、Netty はカスタム コーデックを使用してスティッキー パケットのアンパックの問題を解決します

おすすめ

転載: blog.csdn.net/A_art_xiang/article/details/130290528