之前我们有个netty5的拆包解决方案(参加netty5拆包问题解决实例),现在我们采用另一种思路,不需要新增LengthFieldBasedFrameDecoder,直接修改NettyMessageDecoder:

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;@Overrideprotected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {while (true) {// 标记字节流开始位置byteBuf.markReaderIndex();// 若读取到分割标识,说明读取当前字节流开始位置了if (byteBuf.readInt() == Delimiter.DELIMITER) {break;}// 重置读索引为0byteBuf.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必然也是两两对应,上面的代码中我们只需要用到读标记和读重置。

  我们把客户端handler也修改下,先把LengthFieldBasedFrameDecoder去掉:

// channel.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024 * 1024 * 1024, 4, 4, 2, 0));

  再让数据包更大一些:

    /*** 构造PCM请求消息体** @return*/private byte[] buildPcmData() throws Exception {byte[] resultByte = longToBytes(System.currentTimeMillis());// 读取一个本地文件String AUDIO_PATH = "D:\\input\\test_1.pcm";try (RandomAccessFile raf = new RandomAccessFile(AUDIO_PATH, "r")) {int len = -1;byte[] content = new byte[1024];while((len = raf.read(content)) != -1){resultByte = addAll(resultByte, content);}}return resultByte;}

  再debug下看看,第一次解析客户端发送的数据,读取1024字节,我们可以看到读索引是8(delimiter+length=8),写索引就是1024,我们的大包里有3939116个字节,去掉10个字节的header,剩下小包是3939106::

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

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

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

  从上面看出,我们对ByteBuf的capacity一直在翻倍,读指针一直标记在大包的起始位置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

  其他方法参见测试类:

package com.wlf.netty.nettyserver;import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.junit.Assert;
import org.junit.Test;public class ByteBufTest {@Testpublic void byteBufTest() {ByteBuf byteBuf = Unpooled.buffer(10);byteBuf.writeInt(0xabef0101);byteBuf.writeInt(1024);byteBuf.writeByte((byte) 1);byteBuf.writeByte((byte) 0);// 开始读取printDelimiter(byteBuf);printLength(byteBuf);// 派生一个ByteBuf,取剩下2个字节,但读索引不动ByteBuf duplicatBuf = byteBuf.duplicate();printByteBuf(byteBuf);// 派生一个ByteBuf,取剩下2个字节,读索引动了ByteBuf sliceBuf = byteBuf.readSlice(2);printByteBuf(byteBuf);// 两个派生的对象其实是一样的Assert.assertEquals(duplicatBuf, sliceBuf);}private void printDelimiter(ByteBuf buf) {int newDelimiter = buf.readInt();System.out.printf("delimeter: %s\n", Integer.toHexString(newDelimiter));printByteBuf(buf);}private void printLength(ByteBuf buf) {int length = buf.readInt();System.out.printf("length: %d\n", length);printByteBuf(buf);}private void printByteBuf(ByteBuf buf) {System.out.printf("reader Index: %d, writer Index: %d, capacity: %d\n", buf.readerIndex(), buf.writerIndex(), buf.capacity());}
}

  输出:

delimeter: abef0101
reader Index: 4, writer Index: 10, capacity: 10
length: 1024
reader Index: 8, writer Index: 10, capacity: 10
reader Index: 8, writer Index: 10, capacity: 10
reader Index: 10, writer Index: 10, capacity: 10

ByteBuf使用实例相关推荐

  1. ByteBuf和相关辅助类

    当我们进行数据传输的时候,往往需要使用到缓冲区,常用的缓冲区就是JDK NIO类库提供的java.nio.Buffer. 实际上,7种基础类型(Boolean除外)都有自己的缓冲区实现,对于NIO编程 ...

  2. Netty 系列三(ByteBuf).

    一.概述和原理 网络数据传输的基本单位总是字节,Netty 提供了 ByteBuf 作为它的字节容器,既解决了 JDK API 的局限性,又为网络应用程序提供了更好的 API,ByteBuf 的优点: ...

  3. 《netty实战》阅读笔记(2)——Netty 的数据容器ByteBuf

    ByteBuffer 当我们进行数据传输的时候,往往需要使用到缓冲区,常用的缓冲区就是JDK NIO类库提供的java.nio.Buffer. 实际上,7种基础类型(Boolean除外)都有自己的缓冲 ...

  4. bytebuf池_Netty ByteBuf

    ByteBuf ByteBuf需要提供JDK ByteBuffer的功能(包含且不限于),主要有以下几类基本功能: 7种Java基础类型.byte[].ByteBuffer(ByteBuf)的等的读写 ...

  5. Netty——ByteBuf的API

    ByteBuf 正如前面所提到的,网络数据的基本单位总是字节.Java NIO 提供了 ByteBuffer 作为它 的字节容器,但是这个类使用起来过于复杂,而且也有些繁琐. Netty 的 Byte ...

  6. java 检查bytebuf长度_Netty实战五之ByteBuf

    网络数据的基本单位总是字节,Java NIO 提供了ByteBuffer作为它的字节容器,但是其过于复杂且繁琐. Netty的ByteBuffer替代品是ByteBuf,一个强大的实现,即解决了JDK ...

  7. Netty学习之ByteBuf数据传输

    流经网络的数据总是具有相同的类型:字节.这些字节是如何流动的主要取决于我们所说的网络传输-一个帮助我们抽象底层数据传输机制的概念. netty数据传输 1.1.通过netty的异步网络处理 1.2.传 ...

  8. 【Netty】ByteBuf--Netty的数据容器

    ByteBuf–Netty的数据容器 网络传输的基本单位是字节,在Java NIO中,JDK提供了Buffer接口,以及其相关的实现作为NIO操作 数据的容器,如ByteBuffer等等. 而Nett ...

  9. 网络编程(四):Netty

    简述 基于Netty的知名项目 数据库:Cassandra 大数据处理:Spark.Hadoop 消息中间件:RocketMQ 检索:Elasticsearch 框架:gRPC.Apache Dubb ...

最新文章

  1. react组件回顶部
  2. 10.2.2移动产品离线功能等具体解释----暨4月8日移动《在离线一体化》公开课Qamp;A...
  3. 2017.9.2 校内模拟赛
  4. js中的类、继承、闭包
  5. Lable 换行动态计算高度(2种)
  6. python 获取字符串中的字典_python cookies提取——从字符串到字典(一行Python代码)...
  7. Windows环境中jdk的下载、安装与配置
  8. hadoop2.6分布式环境搭建
  9. inux中tail命令---用于查看文件内容
  10. RandomAccessFile发生java.io.FileNotFoundException
  11. xposed框架_把安卓手机开发到极致的框架xposed
  12. matlab数据拟合polyfit与polyval初等用法
  13. 用MATLAB对语音进行基频搬移,语音信号变声处理系.doc
  14. 三安集成长沙碳化硅制造基地下半年启动投产;龙芯中科正式发布完全自主指令集架构 | 美通企业日报...
  15. 自定义控件之——封装控件(一)
  16. 解决svmtrain已被删除问题
  17. poj 2152 Fire - 经典树形dp
  18. bzoj 2301(Mobius)
  19. 基于android的ipcamera编程,spydroid-ipcamera-master完整实现源码
  20. 电子计算机原理讲义,最新计算机原理讲义资料.doc

热门文章

  1. java历史记录_JavaWeb之商品查看后历史记录代码实现
  2. 程序员都在用的电脑小技巧
  3. Intellij Idea常用插件
  4. Microelectronic Systems
  5. 数据库事务及MySQL实战
  6. 常见vue、js,webpack面试题
  7. Angular—Dom操作
  8. 【基本功】Litho的使用及原理剖析
  9. 【用pandas_alive几行代码绘制竞赛动图】8.城市人口(测试代码+数据集+绘图参数解析)
  10. 金蝶云:2019年云综合收入13.1亿元,云上蝶舞向前一步