Netty4 自定义Decoder,Encoder进行对象传递

   首先咱们必须知道Tcp粘包和拆包的,TCP是个“流”协议,所谓流,就是没有界限的一串数据,TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际数据进行包的划分,一个完整的包可能会被拆分红多个包进行发送,也有可能把多个小的包封装成一个大的数据包进行发送。这里引用Netty官网的User guide里面的图进行说明:html

 

 

Dealing with a Stream-based Transport

One Small Caveat of Socket Buffer

In a stream-based transport such as TCP/IP, received data is stored into a socket receive buffer. Unfortunately, the buffer of a stream-based transport is not a queue of packets but a queue of bytes. It means, even if you sent two messages as two independent packets, an operating system will not treat them as two messages but as just a bunch of bytes. Therefore, there is no guarantee that what you read is exactly what your remote peer wrote. For example, let us assume that the TCP/IP stack of an operating system has received three packets:java

Three packets received as they were sent

Because of this general property of a stream-based protocol, there's high chance of reading them in the following fragmented form in your application:json

Three packets split and merged into four buffers

Therefore, a receiving part, regardless it is server-side or client-side, should defrag the received data into one or more meaningful frames that could be easily understood by the application logic. In case of the example above, the received data should be framed like the following:app

Four buffers defragged into three

 

 

  那么通常状况下咱们是如何解决这种问题的呢?我所知道的有这几种方案:less

    >1.消息定长socket

    >2.在包尾增长一个标识,经过这个标志符进行分割ide

    >3.将消息分为两部分,也就是消息头和消息尾,消息头中写入要发送数据的总长度,一般是在消息头的第一个字段使用int值来标识发送数据的长度。性能

 

  这里以第三种方式为例,进行对象传输。Netty4自己自带了ObjectDecoder,ObjectEncoder来实现自定义对象的序列化,可是用的是java内置的序列化,因为java序列化的性能并非很好,因此不少时候咱们须要用其余序列化方式,常见的有Kryo,Jackson,fastjson,protobuf等。这里要写的其实用什么序列化不是重点,而是咱们怎么设计咱们的Decoder和Encoder。ui

 

  首先咱们写一个Encoder,咱们继承自MessageToByteEncoder<T> ,把对象转换成byte,继承这个对象,会要求咱们实现一个encode方法:this

    @Override
    protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
        byte[] body = convertToBytes(msg);  //将对象转换为byte,伪代码,具体用什么进行序列化,大家自行选择。可使用我上面说的一些
        int dataLength = body.length;  //读取消息的长度
        out.writeInt(dataLength);  //先将消息长度写入,也就是消息头
        out.writeBytes(body);  //消息体中包含咱们要发送的数据
    }

  那么当咱们在Decode的时候,该怎么处理发送过来的数据呢?这里咱们继承ByteToMessageDecoder方法,继承这个对象,会要求咱们实现一个decode方法

  

public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        if (in.readableBytes() < HEAD_LENGTH) {  //这个HEAD_LENGTH是咱们用于表示头长度的字节数。  因为上面咱们传的是一个int类型的值,因此这里HEAD_LENGTH的值为4.
            return;
        }
        in.markReaderIndex();                  //咱们标记一下当前的readIndex的位置
        int dataLength = in.readInt();       // 读取传送过来的消息的长度。ByteBuf 的readInt()方法会让他的readIndex增长4
        if (dataLength < 0) { // 咱们读到的消息体长度为0,这是不该该出现的状况,这里出现这状况,关闭链接。
            ctx.close();
        }

        if (in.readableBytes() < dataLength) { //读到的消息体长度若是小于咱们传送过来的消息长度,则resetReaderIndex. 这个配合markReaderIndex使用的。把readIndex重置到mark的地方
            in.resetReaderIndex();
            return;
        }

        byte[] body = new byte[dataLength];  //  嗯,这时候,咱们读到的长度,知足咱们的要求了,把传送过来的数据,取出来吧~~
        in.readBytes(body);  //
        Object o = convertToObject(body);  //将byte数据转化为咱们须要的对象。伪代码,用什么序列化,自行选择
        out.add(o);  
    }

  固然咱们Netty也有自带的LengthFieldBasedFrameDecoder,可是在使用自定义序列化的时候,我以为仍是本身写比较方便一点,反正总不是要写代码。

  我走过的坑:用读取ByteBuf的使用,必定要注意,其中的方法是否会增长readIndex,否则的话会形成没法正常读到咱们想要的数据。

相关文章
相关标签/搜索