netty(十)源码分析之ByteBuf

来源:互联网 发布:linux系统启动流程简述 编辑:程序博客网 时间:2024/05/16 19:35
通过对netty的API的学习,可以更加游刃有余的使用netty的相关类库
对源码的学习不仅能够从源码层面掌握netty框架,方便日后的维护,拓展和定制而且可以起到触类旁通的作用,拓展读者的知识面,提升编程技能。

当我们进行数据传输的时候,往往需要使用到缓冲区,常用的缓冲区就是JDK提供的java.nio.Buffer,它的实现类如下所示:

实际上7中数据类型(Boolean除外)都有自己的缓冲区实现,对于NIO编程而言,我们主要使用的是ByteBuffer,从功能角度而言,ByteBuffer完全可以满足NIO编程的需要,但是由于NIO编程的复杂性,ByteBuffer也有其局限性,主要缺点如下:

(1)ByteBuffer长度固定,一旦分配完成,它的容量不能动态扩展和收缩,当需要编码的POJO对象大于ByteBuffer容量时,会发生索引越界异常。
(2)ByteBuffer只有一个标识位置的指针position,读写的时候需要手工调用flip()和rewind()等,使用者必须小心谨慎地处理这些API,否则很容易导致程序处理失败。
(3)ByteBuffer的API功能有限,一些高级和实用的特性它不支持,需要使用者自己编程实现。

为了弥补这些不足,Netty提供了自己的ByteBuffer实现————ByteBuf,下面我们一起学习ByteBuf的原理和主要功能。

ByteBuf的工作原理

不同ByteBuf实现类的工作原理不尽相同,我们从ByteBuf的设计原理出发,一起探寻Netty ByteBuf的设计理念。

首先,ByteBuf依然是个Byte数组的缓冲区,它的基本功能应该与JDK的ByteBuffer一致,提供以下几类基本功能。

  • 7中Java基础类型,byte数组,ByteBufer(ByteBuf)等的读写;
  • 缓冲区自身的copy和slice等;
  • 设置网络字节序;
  • 构造缓冲区实例;
  • 操作位置指针等方法。

由于JDK的ByteBuffer已经提供了这些基础能力的实现,因此,Netty ByteBuf的实现可以有两种策略。

  • 参考JDK ByteBuffer的实现,增加额外的功能,解决原ByteBuffer的缺点;
  • 聚合JDK ByteBuffer,通过Facade模式对其进行包装,可以减少自身的代码量,降低实现成本。

ByteBuf通过两个位置指针来协助缓冲区的读写操作,读操作使用readerIndex,写操作使用writerIndex。
readerIndex和writerIndex的取值一开始都是0,随着数据的写入writerIndex会增加,读取数据readerIndex增加,但是它不会超过writerIndex。在读取之后,0~readerIndex就被视为discard的,调用discardReadBytes方法,可以释放这部分空间,它的作用类似ByteBuffer的conpact方法。ReaderIndex和writerIndex之间的数据是可读取的,等价于ByteBufer position和limit之间的数据。WriterIndex和capacity之间的空间是可写的,等价于ByteBuffer limit和capacity之间的可用空间。

由于写操作不修改readerIndex指针,读操作不修改writerIndex指针,因此读写之间不再需要调整位置指针,这极大地简化了缓冲区的读写操作,避免了由于遗漏或者不熟悉flip()操作导致的功能异常。
初始分配的ByteBuf如下图所示:

写入N个字节后的ByteBuf如下图所示:

读取M(<N)个字节之后的ByteBuf如下图:

调用discardReadBytes操作之后的ByteBuf如下图:

调用clear操作之后的ByteBuf如下图:

ByteBuf对write操作进行了封装,由ByteBuf的write操作负责进行剩余可用空间的校验。如果可用缓冲区不足,ByteBuf会自动进行动态扩展。对于使用者而言,不需要关心底层的校验和扩展细节,只要不超过设置的最大缓冲区容量即可。当可用空间不足时,ByteBuf会帮助我们实现自动扩展,这极大地降低了ByteBuf的学习和使用成本,提升了开发效率。校验和扩展的相关代码如下:

public ByteBuf writeByte(int value) {        this.ensureWritable(1);        this.setByte(this.writerIndex++, value);        return this;    }
通过源码分析,我们发现当进行write操作时,会对需要write的自己进行校验。如果可写的字节数小于需要写入的字节数,并且需要写入的字节数小于可写的最大字节数(最大字节数为自己创建buff设置的容量值),就会对缓冲区进行动态扩展。无论缓冲区是否进行了动态扩展,从功能角度看使用者并不感知,这样就简化了上层的应用。
public ByteBuf ensureWritable(int minWritableBytes) {        if(minWritableBytes < 0) {            throw new IllegalArgumentException(String.format("minWritableBytes: %d (expected: >= 0)", new Object[]{Integer.valueOf(minWritableBytes)}));        } else if(minWritableBytes <= this.writableBytes()) {            return this;        } else if(minWritableBytes > this.maxCapacity - this.writerIndex) {            throw new IndexOutOfBoundsException(String.format("writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s", new Object[]{Integer.valueOf(this.writerIndex), Integer.valueOf(minWritableBytes), Integer.valueOf(this.maxCapacity), this}));        } else {            int newCapacity = this.calculateNewCapacity(this.writerIndex + minWritableBytes);            this.capacity(newCapacity);            return this;        }    }

ByteBuf的功能介绍

ByteBuf常用API进行分类说明,讲解它的主要功能,后面我们还会在给出重要API的一些典型用法。


1.顺序读操作(read)
ByteBuf的read操作类似于ByteBuffer的get操作,主要的API功能说梦如下表所示。


2.顺序写操作(write)
ByteBuf的write操作类似于ByteBuffer的put操作,主要的API功能如下表所示:


原创粉丝点击