Netty笔记(2)—— Netty编解码开发

Netty编解码开发

编解码技术

Java序列化的问题:

  1. 无法跨语言
  2. 序列化后码流太大
  3. 序列化性能太低

在这里插入图片描述

如何评判一个编解码框架的优劣

  • 是否支持夸语言,支持的语言种类是否丰富
  • 编码后的码流大小
  • 编解码的性能
  • 类库是否小巧,API使用是否方便
  • 使用者需要手工开发的工作量和难度

MessagePack编解码

MessagePack是一个高效的二进制序列化框架,像JSON一样支持不同语言间的数据交换,但是它的性能更高,序列化之后的码流也更小。

特点如下:

  • 编解码高效,性能高
  • 序列化之后的码流小
  • 支持跨语言

MessagePack官网 https://msgpack.org/

MessagePack简单使用

使用maven引入MessagePack

<dependency>
    <groupId>org.msgpack</groupId>
    <artifactId>msgpack</artifactId>
    <version>0.6.12</version>
</dependency>

简单使用(来自MessagePack官网)

package com.wy.messagepack;


import org.msgpack.MessagePack;
import org.msgpack.template.Templates;
import org.msgpack.type.Value;
import org.msgpack.unpacker.Converter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName MessagePackMode1
 * @Description TODO
 * @Author Wang Yue
 * @Date 2021/2/16 16:41
 */

public class MessagePackMode1 {
    public static void main(String[] args) throws IOException {
        List<String> src = new ArrayList<>();
        src.add("msgpack");
        src.add("kunofs");
        src.add("viver");

        MessagePack messagePack = new MessagePack();
        //序列化
        byte[] raw = messagePack.write(src);

        //使用模板进行反序列化
        List<String> dst1 = messagePack.read(raw, Templates.tList(Templates.TString));
        System.out.println(dst1.get(0));
        System.out.println(dst1.get(1));
        System.out.println(dst1.get(2));

        //反序列化为值,然后进行类型转换
        Value dynamic = messagePack.read(raw);
        List<String> dst2 = new Converter(dynamic).read(Templates.tList(Templates.TString));
        System.out.println(dst2.get(0));
        System.out.println(dst2.get(1));
        System.out.println(dst2.get(2));
    }

}

在Netty中使用MessagePack作为编解码器

编码器开发

package com.wy.netty.messagepack;

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

/**
 * @ClassName MsgPackEncoder
 * @Description TODO
 * @Author Wang Yue
 * @Date 2021/2/16 17:09
 */

public class MsgPackEncoder extends MessageToByteEncoder<Object> {

    @Override
    protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
        MessagePack messagePack = new MessagePack();
        byte[] raw = messagePack.write(msg);
        out.writeBytes(raw);
    }
}

解码器开发

package com.wy.netty.messagepack;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import org.msgpack.MessagePack;
import org.msgpack.type.Value;

import java.util.List;

/**
 * @ClassName MsgpackDecoder
 * @Description TODO
 * @Author Wang Yue
 * @Date 2021/2/16 17:13
 */

public class MsgpackDecoder extends MessageToMessageDecoder<ByteBuf> {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
        final byte[] array;
        final int length = msg.readableBytes();
        array = new byte[length];
        msg.getBytes(msg.readerIndex(), array, 0, length);
        MessagePack messagePack = new MessagePack();
        out.add(messagePack.read(array));
    }
}

使用MessagePack作为编解码器开发Netty服务器与客户端

服务器

package com.wy.netty.messagepack;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;

/**
 * @ClassName NettyServer
 * @Description TODO
 * @Author Wang Yue
 * @Date 2021/2/15 18:02
 */

public class NettyServer {

    public static void main(String[] args) throws InterruptedException {
        //两个group的
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(8);

        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) {
                            //使用Messagepack进行编解码
                            ch.pipeline().addLast("frameDecoder", new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));

                            ch.pipeline().addLast("msgpack decoder", new MsgpackDecoder());

                            ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(2));

                            ch.pipeline().addLast("msgpack encoder", new MsgPackEncoder());

                            ch.pipeline().addLast(new NettyServerHandler());
                        }
                    });
            System.out.println("...服务器 is ready...");

            //启动服务器
            ChannelFuture channelFuture = serverBootstrap.bind(6668).sync();

            //对关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }

}
package com.wy.netty.messagepack;

import com.wy.netty.pojo.User;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

/**
 * @ClassName NettyServerHandler
 * @Description TODO
 * @Author Wang Yue
 * @Date 2021/2/15 18:19
 */

public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    private int count = 1;
    private User user = new User("李四", 20);

    //读取数据
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("客户端发送消息是: " + msg + " 本次是第:" + count++ + "次");
        ctx.writeAndFlush(user);
    }


    //处理异常,一般需要关闭通道
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

客户端

package com.wy.netty.messagepack;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;

/**
 * @ClassName NettyClient
 * @Description TODO
 * @Author Wang Yue
 * @Date 2021/2/15 18:41
 */

public class NettyClient {

    public static void main(String[] args) throws InterruptedException {
        //客户端需要一个事件循环即可
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            //创建客户端启动对象
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY,true)
                    .handler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast("frameDecoder", new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));

                            ch.pipeline().addLast("msgpack decoder", new MsgpackDecoder());

                            ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(2));

                            ch.pipeline().addLast("msgpack encoder", new MsgPackEncoder());
                            //加入自己的处理器
                            ch.pipeline().addLast(new NettyClientHandler());
                        }
                    });

            System.out.println("...客户端 is ok...");
            //启动客户端连接服务器
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
            //给关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

}
package com.wy.netty.messagepack;

import com.wy.netty.pojo.User;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

/**
 * @ClassName NettyClientHandler
 * @Description TODO
 * @Author Wang Yue
 * @Date 2021/2/15 18:50
 */

public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    private int count = 1;

    private User user = new User("张三", 19);



    //当通道就绪时,就会触发该方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 1000; i++) {
            ctx.writeAndFlush(user);
        }
    }

    //当通道有读取事件时,会触发此方法
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("服务器回复消息: " + msg + " 本次是第:" + count++ + "次");
    }

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

粘包/半包支持

上述代码示例中使用 LengthFieldBasedFrameDecoderLengthFieldPrepender实现对粘包和半包的支持。

这种方式就是解决策略中的第三种,即,在消息头中新增报文长度字段,利用该字段进行半包的编解码。

关于LengthFieldBasedFrameDecoder的解析如下,参考https://www.cnblogs.com/crazymakercircle/p/10294745.html

自定义长度解码器LengthFieldBasedFrameDecoder构造器,涉及5个参数,都与长度域(数据包中的长度字段)相关,具体介绍如下:

(1) maxFrameLength - 发送的数据包最大长度;

(2) lengthFieldOffset - 长度域偏移量,指的是长度域位于整个数据包字节数组中的下标;

(3) lengthFieldLength - 长度域的自己的字节数长度。

(4) lengthAdjustment – 长度域的偏移量矫正。 如果长度域的值,除了包含有效数据域的长度外,还包含了其他域(如长度域自身)长度,那么,就需要进行矫正。矫正的值为:包长 - 长度域的值 – 长度域偏移 – 长度域长。

(5) initialBytesToStrip – 丢弃的起始字节数。丢弃处于有效数据前面的字节数量。比如前面有4个节点的长度域,则它的值为4。

LengthFieldBasedFrameDecoder spliter=new LengthFieldBasedFrameDecoder(1024,0,4,0,4);

第一个参数为1024,表示数据包的最大长度为1024;第二个参数0,表示长度域的偏移量为0,也就是长度域放在了最前面,处于包的起始位置;第三个参数为4,表示长度域占用4个字节;第四个参数为0,表示长度域保存的值,仅仅为有效数据长度,不包含其他域(如长度域)的长度;第五个参数为4,表示最终的取到的目标数据包,抛弃最前面的4个字节数据,长度域的值被抛弃。

关于LengthFieldPrepender的解析,参考https://www.cnblogs.com/pc-boke/articles/9293226.html

如果协议中的第一个字段为长度字段,netty提供了LengthFieldPrepender编码器,它可以计算当前待发送消息的二进制字节长度,将该长度添加到ByteBuf的缓冲区头中,如图所示:

在这里插入图片描述

通过LengthFieldPrepender可以将待发送消息的长度写入到ByteBuf的前2个字节,编码后的消息组成为长度字段+原消息的方式。

通过设置LengthFieldPrepender为true,消息长度将包含长度字段占用的字节数,打开LengthFieldPrepender后,图3-3示例中的编码结果如下图所示:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值