[Netty学习笔记]十、编解码器的使用

基本说明

Netty的组件设计:Netty主要组件有Channel、EventLoop、ChannelFuture、ChannelHandler、ChannelPipe等。

ChannelHandler充当了处理入站和出站数据的应用程序逻辑的容器。例如,实现ChannelInboundHandler接口(或ChannelInboundHandlerAdapter),你就可以接收入站事件和数据,这些数据会被业务逻辑处理。业务逻辑通常写在一个或者多个ChannelInboundHandler中。ChannleOutboundHandler原理一样,只是它是用来处理出站数据的。

ChannelPipeline提供了ChannleHandler链的容器。以客户端应用程序为例,如果事件的运动方向是从客户端到服务器端的,那么我们称这些事件是出站的,即客户端发送给服务器端的数据会通过pipeline中的一系列ChannelOutboundHandler,并被这些Handler处理,反之如果事件的运动方向是从服务器端到客户端的则称为入站。

编解码器

当Netty发送或者接收一个消息时,就会发生一次数据转换。入站消息会被解码,从二进制字节流转为对象等格式;如果是出站消息,则业务数据则会被编码为二进制字节流。

Netty提供了一系列实用的编解码器,他们实现了ChannelInboundHandler或者ChannelOutboundHandler接口。这些编解码器的实现类中,channelRead 方法普遍被重写,以编解码。

以入站为例,channelRead方法会被先调用,随后,它将调用由解码器所提供的decode()方法进行解码,并将已经解码的字节转发给ChannelPipeline中的下一个ChannelInboundHandler。

解码器接口ByteToMessageDecoder、编码器接口MessageToByteEncoder,或者直接实现MessageToMessageCodec包含了编解码。当需要自定义编解码器时,只需要实现接口即可。

自定义解码器

例子:

public class ToIntegerDecoder extends ByteToMessageDecoder{
  
  @Override
  protected void decode(ChannleHandlerContext ctx,ByteBuf in,List<Object> out) throws Exception{
    if(in.readableBytes()>=4){
      out.add(in.readInt());
    }
  }
}

说明:

每次入站从ByteBuf中读取4个字节(因为int是占4个字节),将其解码为一个int,然后将它添加到下一个List中。当没有更多元素可以被添加到List中时,list的内容会被发送给下一个ChannleInboundHandler。这种情况下,判断字节数是否>=4是必要的,否则会出现粘包拆包问题

下面以一个例子来说明Netty中handler链的调用顺序

例子要求:

  • 客户端发送long给服务器端
  • 服务器端发送long给客户端

服务器端代码

package com.wojiushiwo.codec;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * Created by myk
 * 2020/1/29 下午7:02
 */
public class NettyCustomCodecServer {
    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {

            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {

                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast("encoder",new LongEncoder());
                            pipeline.addLast("decoder",new LongDecoder());
                            pipeline.addLast(new CustomCodecServerHandler());
                        }
                    });

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

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

//handler
package com.wojiushiwo.codec;

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

/**
 * Created by myk
 * 2020/1/29 下午7:02
 */
public class CustomCodecServerHandler extends SimpleChannelInboundHandler<Long> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
        System.out.println("调用了CustomCodecServerHandler#channelRead0");
        System.out.println("from client:" + msg);

    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("调用了CustomCodecServerHandler#channelReadComplete");
        ctx.writeAndFlush(123456789L);
    }

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

客户端代码

package com.wojiushiwo.codec;

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;

/**
 * Created by myk
 * 2020/1/29 下午7:02
 */
public class NettyCustomCodecClient {
    public static void main(String[] args) {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {

            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast("encoder", new LongEncoder());
                            pipeline.addLast("decoder", new LongDecoder());
                            pipeline.addLast(new CustomCodecClientHandler());
                        }
                    });


            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8091).sync();
            channelFuture.channel().closeFuture().sync();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}
//handler
package com.wojiushiwo.codec;

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

/**
 * Created by myk
 * 2020/1/29 下午7:02
 */
public class CustomCodecClientHandler extends SimpleChannelInboundHandler<Long> {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端CustomCodecClientHandler#channelActive");
        ctx.writeAndFlush(1999999L);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
        System.out.println("客户端CustomCodecClientHandler#channelRead0");
        System.out.println("from server:" + msg);
    }

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

自定义编解码器代码

//编码器
package com.wojiushiwo.codec;

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

/**
 * Created by myk
 * 2020/1/29 下午7:04
 */
public class LongEncoder extends MessageToByteEncoder<Long> {

    @Override
    protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
        System.out.println("调用编码器LongEncoder#encode");
        out.writeLong(msg);
    }

}

//解码器
package com.wojiushiwo.codec;

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

import java.util.List;

/**
 * Created by myk
 * 2020/1/29 下午7:03
 */
public class LongDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {

        System.out.println("调用LongDecoder#decode");
        //long 8个字节
        if (in.readableBytes() >= 8) {
            out.add(in.readLong());
        }

    }
}

服务器端输出:

调用LongDecoder#decode
调用了CustomCodecServerHandler#channelRead0
from client:1999999
调用了CustomCodecServerHandler#channelReadComplete
调用编码器LongEncoder#encode

客户端输出:

客户端CustomCodecClientHandler#channelActive

调用编码器LongEncoder#encode
调用LongDecoder#decode
客户端CustomCodecClientHandler#channelRead0
from server:123456789

通过打印字符串到控制台可以发现调用顺序:

客户端ChannelActive=>客户端LongEncoder#encode=>服务器端LongDecoder#decode=>CustomCodecServerHandler#channelRead0=>CustomCodecServerHandler#channelReadComplete=>服务器端LongEncoder#encode=>客户端LongDecoder#decode=>CustomCodecClientHandler#channelRead0

总结:

  1. 不论解码器handler还是编码器handler,接收的消息类型必须与待处理的消息类型一致,否则该handler不会被执行
  2. 在解码器进行数据解码时,需要判断缓冲区的数据是否足够,否则接收到的结果可能会与期望结果不一致。
解码器-ReplayingDecoder

ReplayingDecoder继承了ByteToMessageDecoder类,使用这个类,我们不需要显式的去判断缓冲区是否足够,该类会自动去做这个事情。泛型S指定了用户状态管理的类型,其中Void表示不需要状态管理。

示例:

使用ReplayingDecoder重构LongDecoder

package com.wojiushiwo.codec;

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

import java.util.List;

/**
 * Created by myk
 * 2020/1/29 下午7:47
 */
public class LongDecoder2 extends ReplayingDecoder<Void> {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
      //不需要判断数据是否足够读取,内部会进行处理判断
        out.add(in.readLong());
    }
}

ReplayingDecoder优缺点:

优点:使用方便

缺点:

  1. 并不是所有的ByteBuf操作都被支持,如果调用了一个不被支持的方法,将会抛出UnsupportOperationException,比如ReplayingDecoderByteBuf#array()方法
  2. ReplayingDecoder在某些情况下会慢于ByteToMessageDecoder,如网络缓慢且消息格式复杂时,消息会拆成了多个碎片,速度变慢。
其他编解码器
  1. LineBasedFrameDecoder:这个类在Netty内部有使用,它使用行尾控制符(\n或者\r\n)作为分隔符来解析数据
  2. DelimiterBasedFrameDecoder:使用自定义的特殊字符作为消息的分割符
  3. HttpObjectDecoder:Http数据的解码器
  4. LengthFieldBasedFrameDecoder:通过指定长度来标识整包信息,这样就可以自动的处理粘包和半包问题
  5. ObjectEncoder:与对象有个的编码器
发布了116 篇原创文章 · 获赞 23 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/zyxwvuuvwxyz/article/details/104113511