`

[转]Netty精粹之玩转NIO缓冲区

阅读更多
Netty精粹之玩转NIO缓冲区
  • 发布时间: 2016/03/13 19:21
  •  
  • 阅读: 1047
  •  
  • 收藏: 16
  •  
  • 点赞: 1
  •  
  • 评论: 0
摘要
在JAVA NIO相关的组件中,ByteBuffer是除了Selector、Channel之外的另一个很重要的组件,它是直接和Channel打交道的缓冲区,通常场景或是从ByteBuffer写入Channel,或是从Channel读入Buffer;而在Netty中,被精心设计的ByteBuf则是Netty贯穿整个开发过程中的核心缓冲区,那么他们俩有什么区别呢?Netty对于缓冲区的设计对于高性能应用又带来了哪些值得借鉴的思路呢?本文在介绍ByteBuffer和ByteBuf基本概念的基础之上对两者进行对比,进而扩展介绍Netty中的ByteBuf大家族。

在JAVA NIO相关的组件中,ByteBuffer是除了Selector、Channel之外的另一个很重要的组件,它是直接和Channel打交道的缓冲区,通常场景或是从ByteBuffer写入Channel,或是从Channel读入Buffer;而在Netty中,被精心设计的ByteBuf则是Netty贯穿整个开发过程中的核心缓冲区,那么他们俩有什么区别呢?Netty对于缓冲区的设计对于高性能应用又带来了哪些值得借鉴的思路呢?本文在介绍ByteBuffer和ByteBuf基本概念的基础之上对两者进行对比,进而扩展介绍Netty中的ByteBuf大家族。

 

JAVA NIO之ByteBuffer

JAVA NIO中,Channel作为通往具有I/O操作属性的实体的抽象,这里的I/O操作通常指readding/writing,而具有I/O操作属性的实体比如I/O设备、文件、网络套接字等等。光有Channel可不行,我们必须为他增加readding/writing的特性,因此JAVA NIO基于Channel扩展WritableByteChannel和ReadableByteChannel接口。由于本文的重点是ByteBuffer,因此我们对于Channel的设计就看到这里,因为有了WritableByteChannel和ReadableByteChannel之后,我们就可以对ByteBuffer进行操作啦,看看他们提供的两个接口:

public int read(ByteBuffer dst) throws IOException;
public int write(ByteBuffer src) throws IOException;

从上面的接口我们可以看到Channel和ByteBuffer之间发生的两个基本行为,即readding/writing。无论是对文件(FileChannel)还是对网络(SocketChannel)的读写,他们都会去实现这两个基本行为。好了,我们已经从总体上认识ByteBuffer在JAVA NIO所处的位置和担当的角色了,下面我们继续深入一点认识ByteBuffer。

ByteBuffer有四个重要的属性,分别为:mark、position、limit、capacity,和两个重要方法分别为:flip和clear。ByteBuffer的底层存储结构对于堆内存和直接内存分别表现为堆上的一个byte[]对象和直接内存上分配的一块内存区域。既然是一块内存区域,那么我们就可以对其进行基于字节的读和写,而ByteBuffer的四个int类型的属性则是指向这块区域的指针:

  1. position:读写指针,代表当前读或写操作的位置,这个值总是小于等于limit的。

  2. mark:在使用ByteBuffer的过程中,如果想要记住当前的position,则会将当前的position值给mark,让需要恢复的时候,再将mark的值给position。

  3. capacity:代表这块内存区域的大小。

  4. limit:初始的Buffer中,limit和capacity的值是相等的,通常在clear操作和flip操作的时候会对这个值进行操作,在clear操作的时候会将这个值和capacity的值设置为相等,当flip的时候会将当前的position的值给limit,我们可以总结在写的时候,limit的值代表最大的可写位置,在读的时候,limit的值代表最大的可读位置。clear是为了写作准备、flip是为了读做准备。

    ByteBuffer指针示意图

在JAVA NIO中,原生的ByteByffer家族成员很简单,主要是HeapByteBuffer、DirectByteBuffer和MappedByteBuffer:

  1. HeapByteBuffer是基于堆上字节数组为存储结构的缓冲区。

  2. DirectByteBuffer是基于直接内存上的内存区域为存储结构的缓冲区。

  3. MappedByteBuffer主要是文件操作相关的,它提供了一种基于虚拟内存映射的机制,使得我们可以像操作文件一样来操作文件,而不需要每次将内容更新到文件之中,同时读写效率非常高。

 

Netty之ByteBuf

相比于ByteBuffer的读写指针position,ByteBuf提供了两个指针readerIndex和writeIndex来分别指向读的位置和写的位置,不需要每次为读写做准备,直接设置读写指针进行读写操作即可。我们看看处于中间状态的状态:

读写中间状态的Buffer

从开始到readerIndex指针之间的这块区域是可以被丢弃的区域,后面会讲到,readerIndex和writerIndex指针之间的区域是可以被读的,writerIndex和capacity指针之间的区域是可以写的区域。当writerIndex指针到达顶端之后,ByteBuf允许用户复用之前已经被读过的区域,调用discardReadBytes方法即可,对应于上面的状态,调用discardReadBytes之后的状态如下:

调用discardReadBytes之后回收可用区域

除了discardReadBytes方法之外,另外一个比较重要的方法就是clear了,clear即清除缓冲区的指针状态,回复到初始值,对应于中间状态的那张图,调用clear之后的状态如下:

调用clear之后,Buffer状态的指针状态得到了初始化

 

Netty ByteBuf的特点

这里想要比较两种Buffer,对比ByteBuffer得出ByteBuf的优点点,我们首先要做的就是总结ByteBuf的特点以及相比ByteBuffer,这个特点如何成为优点:

(1)ByteBuf读写指针

在ByteBuffer中,读写指针都是position,而在ByteBuf中,读写指针分别为readerIndex和writerIndex,直观看上去ByteBuffer仅用了一个指针就实现了两个指针的功能,节省了变量,但是当对于ByteBuffer的读写状态切换的时候必须要调用flip方法,而当下一次写之前,必须要将Buffe中的内容读完,再调用clear方法。每次读之前调用flip,写之前调用clear,这样无疑给开发带来了繁琐的步骤,而且内容没有读完是不能写的,这样非常不灵活。相比之下我们看看ByteBuf,读的时候仅仅依赖readerIndex指针,写的时候仅仅依赖writerIndex指针,不需每次读写之前调用对应的方法,而且没有必须一次读完的限制。

 

(2)ByteBuf引用计数

ByteBuf扩展了ReferenceCountered接口,这个接口定义的功能主要是引用计数:

ReferenceCountered接口定义

也就是所有对ByteBuf的实现,都要实现引用计数,Netty对Buffer资源进行了显式的管理,这部分要结合Netty的内存池技术理解,当Buffer引用+1的时候,需要调用retain来让refCnt+1,当Buffer引用数-1的时候需要调用release来让refCnt-1,当refCnt变为0的时候Netty为pooled和unpooled的不同buffer提供了不同的实现,通常对于非内存池的用法,Netty把Buffer的内存回收交给了垃圾回收器,对于内存池的用法,Netty对内存的回收实际上是回收到内存池内,以提供下一次的申请所使用,关于内存池这部分可以参考我之前的一篇文章

(3)池化Buffer资源

由于Netty是一个NIO网络框架,因此对于Buffer的使用如果基于直接内存(DirectBuffer)实现的话,将会大大提高I/O操作的效率,然而DirectBuffer和HeapBuffer相比之下除了I/O操作效率高之外还有一个天生的缺点,即对于DirectBuffer的申请相比HeapBuffer效率更低,因此Netty结合引用计数实现了PolledBuffer,即池化的用法,当引用计数等于0的时候,Netty将Buffer回收致池中,在下一次申请Buffer的没某个时刻会被复用。Netty这样做的基本想法是我们花了很大的力气申请了一块内存,不能轻易让他被回收呀,能重复利用当然重复利用咯。

(3)ByteBuffer才能和Channel打交道

归根结底,站在NIO的立场上所有的缓冲区要想和Channel打交道,换句话说也就是从网络Channel读取数据的时候,都是从Channel到ByteBuffer,从缓冲区写的网上上的时候,都是从ByteBuffer到Channel。因此,当Netty监听到I/O读事件的时候,会将自己流从Channel读到ByteBuffer而不是ByteBuf,see below:

return in.read((ByteBufferinternalNioBuffer().clear().position(index).limit(index + length));

上面是ByteBuf的其中一个具体的读实现,可以看出ByteBuf维护着一个内部的ByteBuffer,叫做internalNioBuffer。当需要将字节流写入网络的时候,需要将ByteBuf转换为ByteBuffer,see below:

 ByteBuffer tmpBuf;
    if (internal) {
        tmpBuf = internalNioBuffer();
    } else {
        tmpBuf = ByteBuffer.wrap(array);
    }
    return out.write((ByteBuffer) tmpBuf.clear().position(index).limit(index + length));
}

上面是ByteBuf的其中一个具体的写实现,在写之前,总会将ByteBuf变成ByteBuffer。

稍微总结下这一节,ByteBuf本身的设计,在指针方面用两个读写指针分别代表读和写指针,这样做减少了Buffer使用的难度和出错率,概念上去理解也比较简单。在Netty中,每个被申请的Buffer对于Netty来说都可能是很宝贵的资源,因此为了获得对于内存的申请与回收更多的控制权,Netty自己根据引用计数法去实现了内存的管理,另外配合精心设计的池化算法在更大程度上控制了内存的使用,虽然相比单纯的申请-使用-释放来说实现可被管理、可被池化的Buffer是略复杂的,但是能为Netty卓越的性能数据做一些贡献,这绝对是值得的。最后我们要理清概念,JAVA NIO中和Channel打交道的只能是ByteBuffer,Netty在读写之前都有做转换,因此不要搞混,ByteBuf还是ByteBuf,它不是ByteBuffer。

 

Netty的Buffer大家族

这一节介绍一下Netty的Buffer大家族,ByteBuf的家族是庞大的,但是我们可以理清套路来将他们归类一下,这样看起来就不会那么的复杂,Netty主要围绕着2*2的维度进行对Buffer的扩展,他们分别是:

DirectBuffer

HeapBuffer

PooledBuffer

UnPooledBuffer

最高层的抽象是ByteBuf,Netty首先根据直接内存和堆内存,将Buffer按照这两个方向去扩展,之后再分别对具体的直接内存和堆内存缓冲区按照是否池话这两个方向再进行扩展。除了这两个维度,Netty还扩展了基于Unsafe的Buffer,我们分别挑出一个比较典型的实现来进行介绍:

PooledHeapByteBuf:池化的基于堆内存的缓冲区。

PooledDirectByteBuf:池化的基于直接内存的缓冲区。

PooledUnsafeDirectByteBuf:池化的基于Unsafe和直接内存实现的缓冲区。

UnPooledHeapByteBuf:非池化的基于堆内存的缓冲区。

UnPooledDirectByteBuf:非池化的基于直接内存的缓冲区。

UnPooledUnsafeDirectByteBuf:非池化的基于Unsafe和直接内存实现的缓冲区。

除了上面这些,另外Netty的Buffer家族还有CompositeByteBuf、ReadOnlyByteBufferBuf、ThreadLocalDirectByteBuf等等,这里还要说一下UnsafeBuffer,当当前平台支持Unsafe的时候,我们就可以使用UnsafeBuffer,JAVA DirectBuffer的实现也是基于unsafe来对内存进行操作的,我们可以看到不同的地方是PooledUnsafeDirectByteBuf或UnPooledUnsafeDirectByteBuf维护着一个memoryAddress变量,这个变量代表着缓冲区的内存地址,在使用的过程中加上一个offer就可以对内存进行灵活的操作。总的来说,Netty围绕着ByteBuf及其父接口定义的行为分别从是直接内存还是使用堆内存,是池话还是非池化,是否支持Unsafe来对ByteBuf进行不同的扩展实现。

 

参考文献:

《Netty4.0.24.Final源码及其注释》

《JAVA7的NIO包源码》

 

转自:http://my.oschina.net/andylucc/blog/636661

分享到:
评论

相关推荐

    NIO netty开发

    netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty开发之nio netty...

    基于netty的nio使用demo源码

    2. **缓冲区(Buffer)**:缓冲区是NIO的核心,用于存储数据。在Netty中,有多种类型的缓冲区,如ByteBuffer、CharBuffer、DoubleBuffer等,它们都是抽象类AbstractBuffer的子类。 3. **选择器(Selector)**:选择...

    NIO+Netty5视频教程与Netty源码剖析视频教程

    这部分教程主要讲解Netty框架的内部工作机制,包括其设计模式、事件驱动模型、线程模型以及缓冲区等核心概念。Netty是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。...

    Netty精粹之TCP粘包拆包问题

    ### Netty精粹之TCP粘包拆包问题详解 #### 一、引言 在网络通信领域,尤其是在基于TCP协议的应用程序开发中,经常会遇到“粘包”和“拆包”的问题。这些问题虽然属于较为底层的技术细节,但对于保障数据传输的准确...

    Netty--Java IO/NIO 框架 简单学习例子

    然而,NIO 的API相对复杂,使用起来需要深入理解缓冲区(Buffer)、通道(Channel)和选择器(Selector)等概念。 Netty 是基于 NIO 构建的,但它封装了复杂的 NIO API,提供了一套简单易用、高性能的网络编程接口...

    【项目实战】Netty源码剖析&NIO;+Netty5各种RPC架构实战演练三部曲视频教程(未加密)

    相比于传统的阻塞I/O,NIO采用了基于缓冲区和通道的非阻塞模式,减少了线程的开销,并提高了系统的吞吐量。NIO主要由Buffer、Channel、Selector三大组件构成,其中Selector用于多路复用I/O操作。 #### 三、Netty...

    netty示例NIO示例

    myeclipse开发通信示例,框架netty,代码本人写的,而且已测试通过,先运行NettyService,再运行NettyClient即可看到效果。nio示例也有,原理一样,运行先后顺序同netty.

    java_Netty权威指南,详解nio,tcp,http,netty

    2. ByteBuf:Netty自定义的高效字节缓冲区,替代了Java的ByteBuffer,提供了更友好的API和内存管理。 3. 通道处理器:ChannelHandler是处理网络事件的核心接口,可以定制实现各种业务逻辑。 4. 通道管道:...

    Java_NIO框架Netty教程

    资源名称:Java_NIO框架Netty教程资源截图: 资源太大,传百度网盘了,链接在附件中,有需要的同学自取。

    Java_NIO框架Netty教程.pdf

    学习Netty,需要掌握Java NIO的基本概念,如选择器(Selector)、通道(Channel)、缓冲区(Buffer)等,以及Netty框架的使用和设计模式。通过阅读"Java NIO框架Netty教程.pdf",你可以深入理解Netty的工作原理,并...

    jvm、nio、netty优化使用.txt

    Netty是一个NIO客户端服务器框架,可以快速轻松地开发网络应用程序,例如协议服务器和客户端。它极大地简化和简化了TCP和UDP套接字服务器等网络编程。 “快速简便”并不意味着最终的应用程序将遭受可维护性或性能...

    Java-NIO-Netty框架学习

    3. **高性能的缓冲区**:Netty使用ByteBuf作为缓冲区,相较于Java原生的ByteBuffer,提供了更丰富的操作接口和更优秀的性能。 4. **强大的编码解码器**:Netty提供了一系列预定义的编解码器,用于处理各种常见协议...

    Netty进阶之路-跟着案例学Netty

    此外,Netty的ByteBuf类是高效内存管理的关键,它提供了缓冲区的读写操作,避免了不必要的数据拷贝。 在高级特性部分,书籍会涉及Netty的编解码器,如LineBasedFrameDecoder用于处理以换行符分隔的协议,以及...

    Socket 之 BIO、NIO、Netty 简单实现

    Socket是网络编程中的基础组件,它提供了进程间通信(IPC)和网络通信的能力。...通过阅读《Socket 之 BIO、NIO、Netty 简单实现》的博客,你可以了解如何在Java中实现这些通信模型,从而提升网络服务的性能和可靠性。

    Java网络编程 NIO Netty

    2. 高效的缓冲区:Netty自定义了ByteBuf,相比Java NIO的ByteBuffer,提供了更多便捷的操作方法,如合并、复制等。 3. 灵活的编解码器:Netty提供了强大的编码和解码组件,可以处理各种数据格式,如HTTP、TCP、UDP等...

    从NIO到Netty,编程实战出租车905协议-08172347.pdf

    905.4-2014协议,是交通运输部公路科学研究院起草定制的一个协议标准,它也是基于TCP之上的一个应用层传输协议。 第2章,介绍在Socket编程过程中一些基础知识,让大家建立起对这块知识内容的一个整体轮廓; 第3章,...

    《Netty进阶之路 跟着案例学Netty》.rar

    Java进阶技术-netty进阶之路

    Netty01-nio.md

    Netty01-nio.md

    java nio&netty系列之三netty网络模型代码以及简化版代码示例

    总的来说,Netty的网络模型基于Java NIO,利用非阻塞I/O和事件驱动的机制,实现了高并发、低延迟的网络通信。同时,Netty提供的API简单易用,使得开发者可以更加专注于业务逻辑,而不是底层的网络细节。结合源码阅读...

Global site tag (gtag.js) - Google Analytics