之前我们有个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; @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必然也是两两对应,上面的代码中我们只需要用到读标记和读重置。

  我们把客户端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 {
@Test
public 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(7)源码-ByteBuf

    一.ByteBuf工作原理 1. ByteBuf是ByteBuffer的升级版: jdk中常用的是ByteBuffer,从功能角度上,ByteBuffer可以完全满足需要,但是有以下缺点: ByteB ...

  3. 【netty这点事儿】ByteBuf 的使用模式

    堆缓冲区 最常用的 ByteBuf 模式是将数据存储在 JVM 的堆空间中. 这种模式被称为支撑数组(backing array), 它能在没有使用池化的情况下提供快速的分配和释放. 直接缓冲区 直接 ...

  4. Netty实战五之ByteBuf

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

  5. Netty 系列三(ByteBuf).

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

  6. ByteBuf源码

    ByteBuf是顶层的抽象类,定义了用于传输数据的ByteBuf需要的方法和属性. AbstractByteBuf 直接继承ByteBuf,一些公共属性和方法的公共逻辑会在这里定义.例如虽然不同性质的 ...

  7. Netty之ByteBuf

    本文内容主要参考<<Netty In Action>>,偏笔记向. 网络编程中,字节缓冲区是一个比较基本的组件.Java NIO提供了ByteBuffer,但是使用过的都知道B ...

  8. Netty学习摘记 —— ByteBuf详解

    本文参考 本篇文章是对<Netty In Action>一书第五章"ByteBuf"的学习摘记,主要内容为JDK 的ByteBuffer替代品ByteBuf的优越性 你 ...

  9. 《Netty in action》 读书笔记

    声明:这篇文章是记录读书过程中的知识点,并加以归纳总结,成文.文中图片.代码出自<Netty in action>. 1. 为什么用Netty? 每个框架的流行,都一定有它出众的地方.Ne ...

随机推荐

  1. JDK源码那些事儿之ConcurrentLinkedQueue

    阻塞队列的实现前面已经讲解完毕,今天我们继续了解源码中非阻塞队列的实现,接下来就看一看ConcurrentLinkedQueue非阻塞队列是怎么完成操作的 前言 JDK版本号:1.8.0_171 Co ...

  2. 《3+1团队》【Beta】Scrum meeting 2

    项目 内容 这个作业属于哪个课程 任课教师博客主页链接 这个作业的要求在哪里 作业链接地址 团队名称 3+1团队 团队博客地址 https://home.cnblogs.com/u/3-1group ...

  3. 《发际线总是和我作队》第九次作业:Beta冲刺Scrum Meeting3

    项目 内容 这个作业属于哪个课程 软件工程 这个作业的要求在哪里 实验十三 团队作业9:Beta冲刺与团队项目冲刺 团队名称 发际线总和我作队 作业学习目标 (1)掌握软件黑盒测试技术:(2)掌握软件 ...

  4. JavaScript 进阶问题列表

    https://github.com/lydiahallie/javascript-questions/blob/master/zh-CN/README-zh_CN.md 很考基本功

  5. 正确理解cookie和session机制原理

    php中cookie和session是我们常用的两个变量了,一个是用户客户端的,一个用在服务器的但他们的区别与工作原理怎么样,下面我们一起来看看cookie和session机制原理吧. cookie和 ...

  6. jdk、jre、jvm三者联系

    JDK(Java Development Kit)是针对Java开发员的产品,是整个Java的核心,包括了Java运行环境JRE.Java工具和Java基础类库.Java Runtime Enviro ...

  7. c++ templat乱测

    该上机实验环境 linux mint  IDE:qt5.11   代码复制到windows下vs2017报错,提示char* 类型不能直接赋值字符串 在linux mint下可以运行,测试目的:检验复 ...

  8. java利用反射动态获取实体类的属性值

    直接贴代码吧,有需要的话,可以根据自己的需要修改部分代码: public BigDecimal getByAttributeName(ThmdGwqriR thmdGwqriR, String att ...

  9. VOJ 1049送给圣诞夜的礼物——矩阵快速幂模板

    题意 顺次给出 $m$个置换,反复使用这 $m$ 个置换对一个长为 $n$ 初始序列进行操作,问 $k$ 次置换后的序列.$m<=10, k<2^31$. 题目链接 分析 对序列的置换可表 ...

  10. 2019-2020-1 20199302《Linux内核原理与分析》第六周作业

    一 .万能函数 1.过程抽象 (1)接口:指明模块要做什么,标识符/类型.函数等,.h ,函数调用者 (2)实现:指明模块如何完成接口,一个接口多个实现(可能),.c ,函数实现者 (3)函数签名:函 ...