• 基于Protostuff实现的Netty编解码器


    在设计netty的编解码器过程中,有许多组件可以选择,这里由于咱对Protostuff比较熟悉,所以就用这个组件了。由于数据要在网络上传输,所以在发送方需要将类对象转换成二进制,接收方接收到数据后,需要将二进制转换成类对象,由于这个操作在之前的文章中有讲解过:网络传输数据序列化工具Protostuff,所以可以翻看我之前的文章来查看具体的实践方法:

    public class SerializeUtil {
    
        private static class SerializeData{
            private Object target;
        }
    
        @SuppressWarnings("unchecked")
        public static byte[] serialize(Object object) {
            SerializeData serializeData = new SerializeData();
            serializeData.target = object;
            Class<SerializeData> serializeDataClass = (Class<SerializeData>) serializeData.getClass();
            LinkedBuffer linkedBuffer = LinkedBuffer.allocate(1024 * 4);
            try {
                Schema<SerializeData> schema = RuntimeSchema.getSchema(serializeDataClass);
                return ProtostuffIOUtil.toByteArray(serializeData, schema, linkedBuffer);
            } catch (Exception e) {
                throw new IllegalStateException(e.getMessage(), e);
            } finally {
                linkedBuffer.clear();
            }
        }
    
        @SuppressWarnings("unchecked")
        public static <T> T deserialize(byte[] data, Class<T> clazz) {
            try {
                Schema<SerializeData> schema = RuntimeSchema.getSchema(SerializeData.class);
                SerializeData serializeData = schema.newMessage();
                ProtostuffIOUtil.mergeFrom(data, serializeData, schema);
                return (T) serializeData.target;
            } catch (Exception e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
        }
    }

    但是,上面只是普通的操作Util,如何让数据能够在netty上进行传输呢?

    在netty中,如果想发送数据出去,那么需要将数据转换成二进制,然后通过网络传送出去,他提供了MessageToByteEncoder的操作类,用户需要继承此类,然后实现encode方法就可以了。来看看我们如何将我们写好的SerializeUtil操作类集成进去:

    public class NettyMessageEncoder extends MessageToByteEncoder<NettyMessage> {
    
        @Override
        protected void encode(ChannelHandlerContext ctx, NettyMessage msg, ByteBuf out) throws Exception {
            out.writeBytes(SerializeUtil.serialize(msg));
        }
    }

    如上代码所示,我们就准备好了一个基于Protostuff组件实现的编码类了。编码后的数据,被添加到ByteBuf缓冲区后,被发送出去。

    那么如何来实现解码器呢?

    public class NettyMessageDecoder extends LengthFieldBasedFrameDecoder{
    
        public NettyMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) {
            super(maxFrameLength, lengthFieldOffset, lengthFieldLength);
        }
    
        @Override
        public  Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
            try {
                byte[] dstBytes = new byte[in.readableBytes()];
                //in.getBytes(in.readerIndex(), dstBytes);
                //切记这里一定要用readBytes,不能用getBytes,否则会导致readIndex不能向后移动,从而导致netty did not read anything but decoded a message.错误
                in.readBytes(dstBytes,0,in.readableBytes());
                NettyMessage nettyMessage = SerializeUtil.deserialize(dstBytes, NettyMessage.class);
                return nettyMessage;
            } catch (Exception e) {
                System.out.println("exception when decoding: " + e);
                return null;
            }
        }
    }

    如上代码所示。一般情况下,需要继承netty中的ByteToMessageDecoder操作类来实现,但是考虑到这样的话需要用户自己来处理粘包拆包问题,比较麻烦,所以我们就继承自netty中为我们准备好的LengthFieldBasedFrameDecoder来进行,由于此decoder具有处理粘包拆包的功能,而且其继承自ByteToMessageDecoder类,所以就省去了我们处理粘包拆包的逻辑。

    需要注意的是,在进行解码的过程中,我们首先需要从缓冲区读取数据到byte数组中,然后需要将readerIndex标记往后移动,如果读完后不移动的话,会报netty did not read anything but decoded a message的错误,而且这个错误在你运行的时候并不会抛出来,非常隐蔽,要不是细细的调试客户端,根本不能发觉此错误的存在。

    所以从上面代码可以看出,ByteBuf.getBytes,只是单纯的读取缓存区数据,并不会将readerIndex后移。但是ByteBuf.readBytes则会将readerIndex后移。这点必须重视。

    最后,我们将这两个实现类放到handler执行容器中即可。

       channel.pipeline().addLast("nettyMessageDecoder", new NettyMessageDecoder(1024 * 1024, 4, 4));
       channel.pipeline().addLast("nettyMessageEncoder", new NettyMessageEncoder());
       channel.pipeline().addLast("readTimeoutHandler", new ReadTimeoutHandler(50));
       channel.pipeline().addLast("loginAuthResponseHandler", new LoginAuthResponseHandler());
       channel.pipeline().addLast("heartBeatHandler", new HeartBeatResponseHandler());

    最后启动服务,我们就可以看到我们的编解码器正常跑起来了:

    Login is ok: Netty Message [header=Header [crcCode=-1410399999,length=0,sessionId=0,type=4,priority=0,attachment={}]]
    Client send heart beat message to server : ----> Netty Message [header=Header [crcCode=-1410399999,length=0,sessionId=1344,type=5,priority=0,attachment={}]]
    Client receive server heartbeat message : ---> Netty Message [header=Header [crcCode=-1410399999,length=0,sessionId=0,type=6,priority=0,attachment={}]]
  • 相关阅读:
    spring mvc注解文件上传下载
    html,图片上传预览,input file获取文件等相关操作
    three.js、webGL、canvas区别于关联
    html添加新元素兼容和访问
    关于HTML,css3自适应屏幕,自适应宽度
    数据库设计的规则 入门
    mysql 索引入门
    一 .linux上安装 python git redis nginx
    一 .git和github
    一 .Django+Alipay(支付宝支付使用)和微信支付
  • 原文地址:https://www.cnblogs.com/scy251147/p/10342314.html
Copyright © 2020-2023  润新知