基於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={}]]
相關文章
相關標籤/搜索