Loading

Netty——MessagePack编解码

MessagePack编解码

这是一个二进制序列化框架,将用它来改造之前的TimeServer。

添加依赖

<dependency>
    <groupId>org.msgpack</groupId>
    <artifactId>msgpack-core</artifactId>
    <version>${msgpack.version}</version>
</dependency>

这里我用的0.9.1版本。

编写MessagePackEncoder

继承自MessageToByteEncoder,是ChannelOutBoundHandler的子类,主要用于对发送的数据进行处理

public class MessagePackEncoder extends MessageToByteEncoder<Object> {

    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, Object o, ByteBuf byteBuf) throws Exception {
        MessageBufferPacker packer = MessagePack.newDefaultBufferPacker();
        packer.packString((String) o);
        byteBuf.writeBytes(packer.toByteArray());
    }

}

编写MessagePackDecoder

继承自ByteToMessageDecoder,是ChannelInboundHandler的子类

public class MessagePackDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {

        int bufLen = byteBuf.readableBytes();
        byte[] bytes = new byte[bufLen];
        byteBuf.readBytes(bytes);

        MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(bytes);
        list.add(unpacker.unpackString());

    }

}

原书中使用getBytes来读取,但是getBytes是绝对读取,并不会移动ByteBuf的位置,所以netty会抛出一个异常,说我们的Decoder什么都没读,但是它返回了一个结果。可能是老版本的netty允许这个行为,但是新版本不允许了,这里改用readBytes

添加Encoder和Decoder到bootstrap

服务器端

ServerBootstrap bootstrap = new ServerBootstrap()
        .group(bossGroup, workerGroup)
        .channel(NioServerSocketChannel.class)
        .option(ChannelOption.SO_BACKLOG, 1024)
        .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                socketChannel.pipeline().addLast("LengthFieldBasedFrameDecoder", new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
                socketChannel.pipeline().addLast("MessagePackDecoder", new MessagePackDecoder());
                socketChannel.pipeline().addLast("LengthFieldPrepender", new LengthFieldPrepender(2));
                socketChannel.pipeline().addLast("MessagePackEncoder", new MessagePackEncoder());
                socketChannel.pipeline().addLast("TimeServerHandler", new TimeServerHandler());
            }
        });

客户端

Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.TCP_NODELAY, true);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        socketChannel.pipeline().addLast("LengthFieldBasedFrameDecoder", new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
        socketChannel.pipeline().addLast("MessagePackDecoder", new MessagePackDecoder());
        socketChannel.pipeline().addLast("LengthFieldPrepender", new LengthFieldPrepender(2));
        socketChannel.pipeline().addLast("MessagePackEncoder", new MessagePackEncoder());
        socketChannel.pipeline().addLast("TimeClientHandler", new TimeClientHandler());
    }
});

其中LengthFieldBasedFrameDecoderLengthFieldPrepender的作用是在要发送的东西前面添加一个固定字节长度的整数,这里是2个字节,这个整数用来描述后面的内容的长度,这样就能解决粘包和拆包问题。

这里Handler有一个执行顺序的问题,我调了半天,可以去看下这个文章,说的很详细:一文搞懂Netty中Handler的执行顺序

我这篇文章里也简单总结一下。

首先按照我们上面的添加方式,服务器端和客户端的Handler链如下:

服务器:
LengthFieldBasedFrameDecoder -> MessagePackDecoder -> LengthFieldPrepender -> MessagePackEncoder -> TimeServerHandler


客户端:
LengthFieldBasedFrameDecoder -> MessagePackDecoder -> LengthFieldPrepender -> MessagePackEncoder -> TimeClientHandler

这里面,用来解码的DecoderInboundHandler,用来编码的EncoderPrepender也是)是OutboundHandler。一个请求过来,先链的顺序查找并调用InboundHandler,也就是:

服务器:
LengthFieldBasedFrameDecoder -> MessagePackDecoder ->  TimeServerHandler


客户端:
LengthFieldBasedFrameDecoder -> MessagePackDecoder -> TimeClientHandler

也就是数据先经过固定长度头的哪个解码器解码,然后再用MessagePack解码器解码,然后再到我们的TimeClientHandler

关于如何调用OutboundHandler,主要看TimeClientHandler中如何向对方返回数据,如果对方使用ctx.writeAndFlush,那么将从当前Handler的位置反向向前查找并调用所有的OutboundHandler,也就是:

TimeClientHandler -> MessagePackEncoder -> LengthFieldPrepender

TimeServerHandler -> MessagePackEncoder -> LengthFieldPrepender

如果对方使用ctx.channel().writeAndFlush,那么将从当前调用链的尾部反向向前查找并调用所有的OutboundHandler,在本例中是一样的。所以把我们用于处理业务的Handler放到最后能保证所有OutboundHandler都会被调用。如果你修改bootstrap中Handler的顺序:

Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.TCP_NODELAY, true);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        socketChannel.pipeline().addLast("LengthFieldBasedFrameDecoder", new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
        socketChannel.pipeline().addLast("MessagePackDecoder", new MessagePackDecoder());
        // 业务Handler放到中间
        socketChannel.pipeline().addLast("TimeClientHandler", new TimeClientHandler());
        socketChannel.pipeline().addLast("LengthFieldPrepender", new LengthFieldPrepender(2));
        socketChannel.pipeline().addLast("MessagePackEncoder", new MessagePackEncoder());
    }
});

那么如果你使用ctx.writeAndFlush,所有的OutboundHandler都不会被调用,也就是所有的编码器都不会被调用。

posted @ 2022-03-25 15:38  yudoge  阅读(135)  评论(0编辑  收藏  举报