一、简介
数据的传输不是按照原有的磨样进行的,是经过一定的转换的,我们经常用到的也就是ByteBuffer,除此之外还有ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer、CharBuffer。Bytebuffer还有一些子类MappedByteBuffer、DirectByteBuffer、HeapByteBuffer,详见下图
二、ByteBuffer的核心属性
// Invariants: mark <= position <= limit <= capacity private int mark = -1; private int position = 0; private int limit; private int capacity;
以上四个属性必须满足以下要求:mark <= position <= limit <= capacity
看一段代码
public class TestByteBuffer { public static void main(String[] args) { // 获得FileChannel try (FileChannel channel = new FileInputStream("stu.txt").getChannel()) { // 获得缓冲区 ByteBuffer buffer = ByteBuffer.allocate(10); int hasNext = 0; StringBuilder builder = new StringBuilder(); while((hasNext = channel.read(buffer)) > 0) { // 切换模式 limit=position, position=0 buffer.flip(); // 当buffer中还有数据时,获取其中的数据 while(buffer.hasRemaining()) { builder.append((char)buffer.get()); } // 切换模式 position=0, limit=capacity buffer.clear(); } System.out.println(builder.toString()); } catch (IOException e) { } } }
关键的几个步骤是
1、向 buffer 写入数据,例如调用 channel.read(buffer)
2、调用 flip() 切换至读模式,flip会使得buffer中的limit变为position,position变为0,调用 buffer.get()从 buffer 读取数据
3、调用 clear() 或者compact()切换至写模式,调用clear()方法时position=0,limit变为capacity,调用compact()方法时,会将缓冲区中的未读数据压缩到缓冲区前面
三、ByteBuffer的核心方法
1、put()方法
2、flip()方法
3、get()方法
4、rewind()方法
5、clean()方法
此方法为ByteBuffer的方法,而不是Buffer的方法
clear只是对position、limit、mark进行重置,而compact在对position进行设置,以及limit、mark进行重置的同时,还涉及到数据在内存中拷贝(会调用arraycopy)。所以compact比clear更耗性能。但compact能保存你未读取的数据,将新数据追加到为读取的数据之后;而clear则不行,若你调用了clear,则未读取的数据就无法再读取到了,所以需要根据情况来判断使用哪种方法进行模式切换。
四、粘包与半包
网络上有多条数据发送给服务端,数据之间使用 \n 进行分隔,但由于某种原因这些数据在接收时,被进行了重新组合,例如原始数据有3条为:
Hello,nikou\n
I’m yumi\n
How are you?\n
变成了
Hello,nikou\nI’m y
umi\nHow are you?\n
粘包的原因:发送方在发送数据时,并不是一条一条地发送数据,而是将数据整合在一起,当数据达到一定的数量后再一起发送。这就会导致多条信息被放在一个缓冲区中被一起发送出去。
半包的原因:接收方的缓冲区的大小是有限的,当接收方的缓冲区满了以后,就需要将信息截断,等缓冲区空了以后再继续放入数据。这就会发生一段完整的数据最后被截断的现象。
解决的办法有很多,这里先看其中的一种,
1、通过get(index)方法遍历ByteBuffer,遇到分隔符时进行处理。注意:get(index)不会改变position的值。
记录该段数据长度,以便于申请对应大小的缓冲区;
将缓冲区的数据通过get()方法写入到target中;
2、调用compact方法切换模式,因为缓冲区中可能还有未读的数据。
public class ByteBufferDemo { public static void main(String[] args) { ByteBuffer buffer = ByteBuffer.allocate(32); // 模拟粘包+半包 buffer.put("Hello,world\nI'm Nyima\nHo".getBytes()); // 调用split函数处理 split(buffer); buffer.put("w are you?\n".getBytes()); split(buffer); } private static void split(ByteBuffer buffer) { // 切换为读模式 buffer.flip(); for(int i = 0; i < buffer.limit(); i++) { // 遍历寻找分隔符 // get(i)不会移动position if (buffer.get(i) == '\n') { // 缓冲区长度 int length = i+1-buffer.position(); ByteBuffer target = ByteBuffer.allocate(length); // 将前面的内容写入target缓冲区 for(int j = 0; j < length; j++) { // 将buffer中的数据写入target中 target.put(buffer.get()); } // 打印查看结果 ByteBufferUtil.debugAll(target); } } // 切换为写模式,但是缓冲区可能未读完,这里需要使用compact buffer.compact(); } }
参考连接:https://nyimac.gitee.io/2021/04/18/Netty%E5%AD%A6%E4%B9%A0%E4%B9%8BNIO%E5%9F%BA%E7%A1%80/