基于Protostuff实现的Netty编解码器

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

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缓冲区后,被发送出去。网络

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

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类,因此就省去了咱们处理粘包拆包的逻辑。ide

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

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

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

   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={}]]
相关文章
相关标签/搜索