栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

netty之Protostuff序列化协议

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

netty之Protostuff序列化协议

Protostuff序列化详解
  • 前言
  • 依赖
  • 定义编解码器
    • 工具类
    • 编码器
    • 解码器
  • 整合到netty
  • 注意事项

前言

比起上文的Protobuf协议,Protostuff的好处就是不用去编辑proto文件,然后生成对应的java文件,直接可以利用现有的实体类来通信,而且性能性能损失也是很少的。

github官方链接地址

依赖
	
	     io.protostuff
	     protostuff-core
	     1.7.2
	 
	
	 
	     io.protostuff
	     protostuff-runtime
	     1.7.2
	 
定义编解码器

Protobuf有netty提供已经封装好的编解码器,但是Protostuff需要自己定义。

工具类
public class ProtostuffUtils {

    
    private static Map, Schema> schemaCache = new ConcurrentHashMap<>();

    
    @SuppressWarnings("unchecked")
    public static  byte[] serialize(T obj) {
        Class clazz = (Class) obj.getClass();
        Schema schema = getSchema(clazz);
        LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
        byte[] data;
        try {
            data = ProtobufIOUtil.toByteArray(obj, schema, buffer);
        } finally {
            buffer.clear();
        }

        return data;
    }

    
    public static  T deserialize(byte[] data, Class clazz) {
        Schema schema = getSchema(clazz);
        T obj = schema.newMessage();
        ProtobufIOUtil.mergeFrom(data, obj, schema);
        return obj;
    }

    @SuppressWarnings("unchecked")
    private static  Schema getSchema(Class clazz) {
        Schema schema = (Schema) schemaCache.get(clazz);
        if (Objects.isNull(schema)) {
            //这个schema通过RuntimeSchema进行懒创建并缓存
            //所以可以一直调用RuntimeSchema.getSchema(),这个方法是线程安全的
            schema = RuntimeSchema.getSchema(clazz);
            if (Objects.nonNull(schema)) {
                schemaCache.put(clazz, schema);
            }
        }
        return schema;
    }
}
编码器

这里采用分隔符的方式来解决拆包粘包的问题,那么分隔符实验了多次,如果用换行符n,会报错,错误信息如下:

Protocol message contained an invalid tag (zero)

好像是因为的问题,所以只能采用不带的分隔符了,这里选用@来作为分隔符。

public class ProtostuffEncoder extends MessageToByteEncoder {

    //分隔符@,放在配置文件,方便修改配置
    private String delimiter;

    public ProtostuffEncoder(String delimiter) {
        this.delimiter = delimiter;
    }

    @Override
    protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
        if (msg instanceof ComputeResult) {
            byte[] bytes = ProtostuffUtils.serialize(msg);
            byte[] delimiterBytes = delimiter.getBytes();
            byte[] total = new byte[bytes.length + delimiterBytes.length];
            System.arraycopy(bytes, 0, total, 0, bytes.length);
            System.arraycopy(delimiterBytes, 0, total, bytes.length, delimiterBytes.length);
            out.writeBytes(total);
        }
    }
}
解码器
public class ProtostuffDecoder extends ByteToMessageDecoder {

	// 写在这里,方便扩展
    private Class clazz;

    public ProtostuffDecoder(Class clazz) {
        this.clazz = clazz;
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List list) {
        try {
            byte[] body = new byte[in.readableBytes()];  //传输正常
            in.readBytes(body);
            list.add(ProtostuffUtils.deserialize(body, clazz));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 
整合到netty 

这里以客户端为例,服务端同理。

启动类

@Component
@Slf4j
public class NettyClient {

    @Value("${monitor.server.host}")
    private String host;

    @Value("${monitor.server.port}")
    private int port;

    @Value("${monitor.delimiter}")
    private String delimiter;

    @Autowired
    private NettyClientHandler nettyClientHandler;


    private NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();

    private Bootstrap bootstrap;

    @PostConstruct
    public void run() throws UnknownHostException {
        bootstrap = new Bootstrap();
        bootstrap.group(eventLoopGroup)
                .channel(NioSocketChannel.class)
                .remoteAddress(host, port)
                .option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .handler(new ChannelInitializer() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) {
                        //客户端初始化
                        socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024*8, Unpooled.wrappedBuffer(delimiter.getBytes())));
                        socketChannel.pipeline().addLast(new ProtostuffDecoder(ComputeResult.class));
                        socketChannel.pipeline().addLast(new ProtostuffEncoder(delimiter));
                        socketChannel.pipeline().addLast(nettyClientHandler);
                    }
                });
        String hostAddress = InetAddress.getLocalHost().getHostAddress();
        // 指定本机ip端口,用来给服务端区分,指定端口,重启客户端会等两分钟才能连接上服务端
        bootstrap.localAddress(hostAddress,0);
        //连接netty服务器
        reconnect();
    }

    
    public void reconnect() {
        ChannelFuture channelFuture = bootstrap.connect();
        //使用最新的ChannelFuture -> 开启最新的监听器
        channelFuture.addListener((ChannelFutureListener) future -> {
            if (future.cause() != null) {
                log.error("连接失败。。。");
                future.channel().eventLoop().schedule(() -> reconnect(), 3, TimeUnit.SECONDS);
            } else {
                log.info("客户端连接成功。。。");
            }
        });
    }
    
    @PreDestroy
    public void shutdown() {
        // 优雅关闭 EventLoopGroup 对象
        eventLoopGroup.shutdownGracefully();
        log.info("[*Netty客户端关闭]");
    }
}

处理器

@Component
@Slf4j
@ChannelHandler.Sharable
public class NettyClientHandler extends SimpleChannelInboundHandler {

    
    @Autowired
    private NettyClient nettyClient;

    
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        ComputeResult computeResult = new ComputeResult();
        computeResult.setComtype("aaaaaa");
        computeResult.setFromname("bbbbb");
        computeResult.setJunctionnum("ccccccc");
        ctx.writeAndFlush(computeResult);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.error("[*The netty server suspends service...]");
        super.channelInactive(ctx);
        ctx.fireChannelInactive();
        nettyClient.reconnect();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        log.error("[* Netty connection exception]:{}", cause.toString());
        cause.printStackTrace();
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, ComputeResult computeResult) {
        System.out.println(computeResult);
    }
}
注意事项

protostuff的序列化和反序列化特点是一个对象序列化时是按照可序列化字段顺序把值序列化到字节码中,反序列化时也是按照当前对象可序列化字段顺序赋值,如果序列化前的对象和反序列化接收的对象,对应顺序或者字段类型不一样时会出现反序列失败报错。为了避免以上问题,在使用protostuff序列化时,一定要保证客户端和服务端实体类字段顺序一致,且字段类型一致。

案例一:实体类字段顺序不一致

客户端

服务端

如果是这样,客户端传递comtype的值,将会被赋予给服务端的fromnum,因为是按顺序来序列化的。

案例二:实体类字段类型不一致

客户端

服务端

如果是这样,那么服务端反序列化将会报错,报错如下:

所以,一定要保证双方实体类字段的顺序和类型一致。

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号