Chapitre 8 Codec Netty et mécanisme d'appel du gestionnaire

8.1 Instructions de base

  1. Conception des composants de Netty: Les principaux composants de Netty sont Channel, EventLoop, ChannelFuture, ChannelHandler, ChannelPipe, etc.

  2. ChannelHandler agit comme un conteneur pour la logique d'application qui traite les données entrantes et sortantes. Par exemple, en implémentant l'interface ChannelInboundHandler (ou ChannelInboundHandlerAdapter), vous pouvez recevoir des événements et des données entrants, qui seront traités par la logique métier. Lorsque vous souhaitez envoyer une réponse au client, vous pouvez également vider les données de ChannelInboundHandler. La logique métier est généralement écrite dans un ou plusieurs ChannelInboundHandler. ChannelOutboundHandler a le même principe, sauf qu'il est utilisé pour traiter les données sortantes

  3. ChannelPipeline fournit un conteneur pour la chaîne ChannelHandler. Prenons l'exemple de l'application cliente, si la direction des événements va du client vers le serveur, alors nous appelons ces événements sortants , c'est-à-dire que les données envoyées par le client au serveur passeront par une série de ChannelOutboundHandler dans le pipeline Ces traitements de gestionnaire, autrement appelés entrants
    Insérez la description de l'image ici

8.2 Codec

  1. Lorsque Netty envoie ou reçoit un message, une conversion de données se produit. Les messages entrants seront décodés: des octets vers un autre format (tels que les objets java); s'il s'agit d'un message sortant, il sera codé en octets.

  2. Netty fournit une série de codecs pratiques qui implémentent tous l'interface ChannelInboundHadnler ou ChannelOutboundHandler. Dans ces classes, la méthode channelRead a été réécrite. Prenant comme exemple entrant, cette méthode sera appelée pour chaque message lu à partir du canal entrant. Par la suite, il appellera la méthode decode () fournie par le décodeur pour décoder et transmettre les octets décodés au ChannelInboundHandler suivant dans le ChannelPipeline.

8.3 Decoder-ByteToMessageDecoder

  1. Diagramme d'héritage de relation
    Insérez la description de l'image ici

  2. Comme il est impossible de savoir si le nœud distant enverra un message complet à la fois, tcp peut avoir le problème de coller et de décompresser. Cette classe met en mémoire tampon les données entrantes jusqu'à ce qu'elles soient prêtes à être traitées.

  3. Un exemple d'analyse de ByteToMessageDecoder
    Insérez la description de l'image ici
    Insérez la description de l'image ici
    Insérez la description de l'image ici

8.4 Mécanisme d'appel de la chaîne de gestion de Netty

Exemples d'exigences:

  1. Utilisez un encodeur et un décodeur personnalisés pour illustrer le mécanisme d'appel du gestionnaire de Netty Le
    client envoie long-> le serveur Le serveur
    envoie long-> le client
    Insérez la description de l'image ici
    Insérez la description de l'image ici
    Insérez la description de l'image ici
    Insérez la description de l'image ici
  2. Présentation de cas
Server端
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 MyServerInitializer()); //自定义一个初始化类


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

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

    }
}


public class MyServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {//出栈,入栈的Handler顺序是不会冲突的
        ChannelPipeline pipeline = ch.pipeline();//一会下断点

        //入站的handler进行解码 MyByteToLongDecoder
        pipeline.addLast(new MyByteToLongDecoder());

        //出站的handler进行编码
        pipeline.addLast(new MyLongToByteEncoder());

        //自定义的handler 处理业务逻辑
        pipeline.addLast(new MyServerHandler());
        System.out.println("xx");
    }
    /**
     * 执行顺序:
     * MyByteToLongDecoder ----> MyServerHandler
     */
}

public class MyByteToLongDecoder extends ByteToMessageDecoder {
    /**
     *
     * decode 会根据接收的数据,被调用多次, 直到确定没有新的元素被添加到list
     * , 或者是ByteBuf 没有更多的可读字节为止
     * 如果list out 不为空,就会将list的内容传递给下一个 channelinboundhandler处理, 该处理器的方法也会被调用多次
     *
     * 如果Client发送16个字节的话,会被调用2次,同时下一个Handler也会执行2次,下面的结果
     *        xx
     *        MyByteToLongDecoder 被调用
     *        从客户端/127.0.0.1:52740 读取到long 7017280452178371428
     *        MyByteToLongDecoder 被调用
     *        从客户端/127.0.0.1:52740 读取到long 7017280452178371428
     *
     * @param ctx 上下文对象
     * @param in  入站的 ByteBuf
     * @param out List 集合,将解码后的数据传给下一个handler,InboundHandler
     * @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());
        }
    }
}
}

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);
    }
}

public class MyServerHandler extends SimpleChannelInboundHandler<Long> {//MyByteToLongDecoder传来的是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();
    }
}

Client端
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 MyClientInitializer()); //自定义一个初始化类

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

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

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

public class MyClientInitializer extends 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());//这里是入栈的Handler
    }
    /**
     * 执行顺序:
     * MyClientHandler -----> MyLongToByteEncoder
     *     MyClientHandler 发送数据
     *     MyLongToByteEncoder encode 被调用
     *     msg=123456
     */
}

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);
    }
}

public class MyByteToLongDecoder extends ByteToMessageDecoder {

    /**
     *
     * decode 会根据接收的数据,被调用多次, 直到确定没有新的元素被添加到list
     * , 或者是ByteBuf 没有更多的可读字节为止
     * 如果list out 不为空,就会将list的内容传递给下一个 channelinboundhandler处理, 该处理器的方法也会被调用多次
     *
     * 如果Client发送16个字节的话,会被调用2次,同时下一个Handler也会执行2次,下面的结果
     *        xx
     *        MyByteToLongDecoder 被调用
     *        从客户端/127.0.0.1:52740 读取到long 7017280452178371428
     *        MyByteToLongDecoder 被调用
     *        从客户端/127.0.0.1:52740 读取到long 7017280452178371428
     *
     * @param ctx 上下文对象
     * @param in  入站的 ByteBuf
     * @param out List 集合,将解码后的数据传给下一个handler,InboundHandler
     * @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());
        }
    }
}
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 发送数据");

        //这里是Client端的 InboundHandler flush数据,从Client端的OutboundHandler发送出去到Socket到Server
        ctx.writeAndFlush(123456L); //发送的是一个long



        //如果发送下面的字符串“abcdabcdabcdabcd”,在Server会被调用2次,16个字节,一次读取8个字节,会读取2次,
        // 但是这个时候 MyLongToByteEncoder 不会执行(原因下面有分析),Server会出现如下结果
        /**
         * xx
         * MyByteToLongDecoder 被调用
         * 从客户端/127.0.0.1:52740 读取到long 7017280452178371428
         * MyByteToLongDecoder 被调用
         * 从客户端/127.0.0.1:52740 读取到long 7017280452178371428
         *
         */
        //分析  MyLongToByteEncoder 不会执行的原因
        //1. "abcdabcdabcdabcd" 是 16个字节
        //2. 该处理器的前一个handler 是  MyLongToByteEncoder
        //3. MyLongToByteEncoder 父类   MessageToByteEncoder
        //4. 父类  MessageToByteEncoder
        /*
         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);
            }
        }
        4. 因此我们编写 Encoder 是要注意传入的数据类型和处理的数据类型一致
        */
       // ctx.writeAndFlush(Unpooled.copiedBuffer("abcdabcdabcdabcd",CharsetUtil.UTF_8));

    }
}

Insérez la description de l'image ici
3) Conclusion

  • Que le gestionnaire de décodeur ou le gestionnaire d'encodeur reçoive ou non le type de message, il doit être cohérent avec le type de message à traiter, sinon le gestionnaire ne sera pas exécuté
  • Lorsque le décodeur décode les données, il est nécessaire de juger si les données dans la zone tampon (ByteBuf) sont suffisantes, sinon le résultat reçu pourrait être incohérent

8.5 Decoder-ReplayingDecoder

  1. public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder

  2. ReplayingDecoderElle étend la ByteToMessageDecoderclasse, utilisez la classe, nous ne devons pas appeler la méthode readableBytes (). Le paramètre S spécifie le type de gestion d'état utilisateur, où Void signifie qu'aucune gestion d'état n'est requise

  3. Exemple d' application: Utilisation du ReplayingDecoderdécodeur d'écriture du cas simplifié ci - dessus [scénario illustre]

public class MyByteToLongDecoder2 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());
        /** 不用判断了
        if(in.readableBytes() >= 8) {
            out.add(in.readLong());
        }**/
    }
}
  1. ReplayingDecoder est facile à utiliser, mais il a aussi quelques limitations:
  • Toutes les opérations ByteBuf ne sont pas prises en charge. Si une méthode non prise en charge est appelée, une exception UnsupportedOperationException sera levée
    .
  • ReplayingDecoder peut être légèrement plus lent que ByteToMessageDecoder dans certains cas, par exemple, lorsque le réseau est lent et que le format du message est complexe, le
    message est divisé en plusieurs fragments, la vitesse devient lente

8.6 Autres codecs

8.6.1 Autres décodeurs

  1. LineBasedFrameDecoder: Cette classe est également utilisée dans Netty. Elle utilise des caractères de contrôle de fin de ligne (\ n ou \ r \ n) comme délimiteurs pour analyser les données.

  2. DelimiterBasedFrameDecoder: Utilisez des caractères spéciaux personnalisés comme séparateur du message.

  3. HttpObjectDecoder: Un décodeur pour les données HTTP

  4. LengthFieldBasedFrameDecoder: Identifiez l'intégralité du message de paquet en spécifiant la longueur, afin que les messages collants et les demi-paquets puissent être traités automatiquement.

8.6.2 Autres encodeurs
Insérez la description de l'image ici

8.7 Intégration de Log4j dans Netty

  1. Ajouter une dépendance à Log4j dans pom.xml dans Maven
 <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.25</version>
            <scope>test</scope>
        </dependency>

Insérez la description de l'image ici

A publié 138 articles originaux · J'aime 3 · Visiteur 7226

Je suppose que tu aimes

Origine blog.csdn.net/weixin_43719015/article/details/105300626
conseillé
Classement