ByteBuf使用实例

Posted wuxun1997

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ByteBuf使用实例相关的知识,希望对你有一定的参考价值。

  之前我们有个netty5的demo(参加netty5自定义私有协议实例),里面有个NettyMessageDecoder类针对拆包的解码有问题,我们修复下这个bug:

package com.wlf.netty.nettyapi.msgpack;

import com.wlf.netty.nettyapi.constant.Delimiter;
import com.wlf.netty.nettyapi.javabean.Header;
import com.wlf.netty.nettyapi.javabean.NettyMessage;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

public class NettyMessageDecoder extends ByteToMessageDecoder {

    /**
     * 消息体字节大小:分割符字段4字节+长度字段4字节+请求类型字典1字节+预留字段1字节=10字节
     */
    private static final int HEAD_LENGTH = 10;

    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {

        while (true) {

            // 标记字节流开始位置
            byteBuf.markReaderIndex();

            // 若读取到分割标识,说明读取当前字节流开始位置了
            if (byteBuf.readInt() == Delimiter.DELIMITER) {
                break;
            }

            // 重置读索引为0
            byteBuf.resetReaderIndex();

            // 长度校验,字节流长度至少10字节,小于10字节则等待下一次字节流过来
            if (byteBuf.readableBytes() < HEAD_LENGTH) {
                byteBuf.resetReaderIndex();
                return;
            }
        }

        // 2、获取data的字节流长度
        int dataLength = byteBuf.readInt();

        // 校验数据包是否全部发送过来,总字节流长度(此处读取的是除去delimiter和length之后的总长度)-
        // type和reserved两个字节=data的字节流长度
        int totalLength = byteBuf.readableBytes();
        if ((totalLength - 2) < dataLength) {

            // 长度校验,字节流长度少于数据包长度,说明数据包拆包了,等待下一次字节流过来
            byteBuf.resetReaderIndex();
            return;
        }

        // 3、请求类型
        byte type = byteBuf.readByte();

        // 4、预留字段
        byte reserved = byteBuf.readByte();


        // 5、数据包内容
        byte[] data = null;
        if (dataLength > 0) {
            data = new byte[dataLength];
            byteBuf.readBytes(data);
        }

        NettyMessage nettyMessage = new NettyMessage();
        Header header = new Header();
        header.setDelimiter(Delimiter.DELIMITER);
        header.setLength(dataLength);
        header.setType(type);
        header.setReserved(reserved);
        nettyMessage.setHeader(header);
        nettyMessage.setData(data);

        list.add(nettyMessage);

        // 回收已读字节
        byteBuf.discardReadBytes();
    }
}

  我们的改动很小,只不过将原来的读索引改为标记索引,然后在拆包时退出方法前重置读索引,这样下次数据包过来,我们的读索引依然从0开始,delimiter的标记就可以读出来,而不会陷入死循环了。

  ByteBuf是ByteBuffer的进化版,ByteBuffer(参见ByteBuffer使用实例)才一个索引,读写模式需要通过flip来转换,而ByteBuf有两个索引,readerIndex读索引和writerIndex写索引,读写转换无缝连接,青出于蓝而胜于蓝:

      +-------------------+------------------+------------------+
      | discardable bytes |  readable bytes  |  writable bytes  |
      |                           |     (CONTENT)    |                         |
      +-------------------+------------------+------------------+
      |                           |                            |                         |
      0      <=      readerIndex   <=   writerIndex    <=    capacity

  既然有两个索引,那么标记mask、重置reset必然也是两两对应,上面的代码中我们只需要用到读标记和读重置。

  详细过程看下debug,我们的大包里有3939116个字节,去掉10个字节的header,剩下小包是3939106:

  第一次读1024,我们可以看到读索引是8(delimiter+length=8),写索引就是1024:

技术图片

 

   第二次再读1024,代码已经执行reset重置读索引了,所以读索引由8改为0,写索引累增到2048:

技术图片

 

   第三次再读1024,写索引继续累增到3072:

技术图片

 

   最后一次发1024,写索引已经到达3939116,大包传输结束了:

技术图片

   从上面看出,我们对ByteBuf的读指针一直标记在大包的起始位置0,这样做的目的是每次都能读取小包的长度length(3939106),拿来跟整个ByteBuf的长度作比较,只要它取到的小包没到达到length,我们就继续接受新包,写索引不停的累加,直到整个大包长度>=3939116(也就是小包>=3939106),这时我们开始移动读索引,将字节流写入对象,最后回收已读取的字节(调用discardReaderBytes方法):

  BEFORE discardReadBytes()

      +-------------------+------------------+------------------+
      | discardable bytes |  readable bytes  |  writable bytes  |
      +-------------------+------------------+------------------+
      |                         |                      |                            |
      0      <=      readerIndex   <=   writerIndex    <=    capacity


  AFTER discardReadBytes()

      +------------------+--------------------------------------+
      |  readable bytes  |    writable bytes (got more space)   |
      +------------------+--------------------------------------+
      |                        |                                               |
readerIndex (0) <= writerIndex (decreased)        <=        capacity

以上是关于ByteBuf使用实例的主要内容,如果未能解决你的问题,请参考以下文章

Netty入门——ByteBuf

netty里的ByteBuf扩容源码分析

创建片段而不从 java 代码实例化它

Netty 框架中ByteBuf转String

如何为 XSLT 代码片段配置 CruiseControl 的 C# 版本?

创建片段的新实例时菜单未膨胀