NIO buffer和netty buffer

NIO中的Buffer
前面提到过,在NIO中同样存在一个缓冲区,叫做ByteBuffer,来配合Channel的使用。在ByteBuffer内部存储数据的实质为一个字节数组,如:final byte[] hb,并定义了四个标记来管理它。其中包括:mark <= position <= limit <= capacity。其中capacity用来表示缓冲区的大小;position用来标识下一个可读取或者写入的位置;limit表示读取或者写入的上限位置,如果要在>=limit的位置做读写操作会抛出异常;mark用来记录当前position的值,记录之后position随着读写发生变化,在调用reset()方法时,会将position恢复为mark记录的值。在buffer中提供了很多put、get方法来放入和读取数据,这里不多做介绍,可以查看API。但其中有几个重要的方法需要关注:
1. flip()方法:在读取或者写入n个字节(position + n < limit)后,position += n。如果是先读取数据到buffer后写入到Channel,必须将position的值回退到起初的值,并且将limit设置为有效位置,才能让读入的数据真正的写入Channel。调用flip()方法后,buffer中的四个标记会发生以下变化:

Java代码
limit = position;
position = 0;
mark = -1;

2. clear()方法:同样的,将buffer中的数据写入Channel后,再读取一些数据到buffer中,此时往往需要将各标记的值归位,当做一个新的buffer来使用(当然也有特殊情况)。调用clear()方法后,标记变化如下:

Java代码
position = 0;
limit = capacity;
mark = -1;

3. rewind()方法:如果发现刚才从Channel读取的数据需要重新读取,可以调用该方法。调用后标记变化如下:

Java代码
position = 0;
mark = -1;
尤其是flip()和clear()方法,在使用的过程中会频繁用到,否则会造成读取和写入的错乱。
ByteBuffer主要有两个继承的类分别是:HeapByteBuffer和MappedByteBuffer。他们的不同之处在于HeapByteBuffer会在JVM的堆上分配内存资源,而MappedByteBuffer的资源则会由JVM之外的操作系统内核来分配。DirectByteBuffer继承了MappedByteBuffer,采用了直接内存映射的方式,将文件直接映射到虚拟内存,同时减少在内核缓冲区和用户缓冲区之间的调用,尤其在处理大文件方面有很大的性能优势。但是在使用内存映射的时候会造成文件句柄一直被占用而无法删除的情况,网上也有很多介绍。

Netty中的Buffer
Netty中使用ChannelBuffer来处理读写,之所以废弃ByteBuffer,官方说法是ChannelBuffer简单易用并且有性能方面的优势。在ChannelBuffer中使用ByteBuffer或者byte[]来存储数据。同样的,ChannelBuffer也提供了几个标记来控制读写并以此取代ByteBuffer的position和limit,分别是:
0 <= readerIndex <= writerIndex <= capacity,同时也有类似于mark的markedReaderIndex和markedWriterIndex。当写入buffer时,writerIndex增加,从buffer中读取数据时readerIndex增加,而不能超过writerIndex。有了这两个变量后,就不用每次写入buffer后调用flip()方法,方便了很多。

在ChannelBuffer中有几个重要的类继承,AbstractChannelBuffer中实现了基本的方法;HeapChannelBuffer是对NIO中heapBuffer的封装,它有两个继承类:BigEndianHeapChannelBuffer和LittleEndianHeapChannelBuffer(试想,我们将一个int类型(32位)的数据放入内存中,内存会以什么样的顺序放入这32位的数据呢?这就分为big-endian和little-endian的字节序,big-endian就是说将数据的高位放在内存地址更小的位置,little-endian是将低位放在内存地址更小的位置,选择和所用硬件和操作系统相同的字节序有利于提高性能);ByteBufferBackedChannelBuffer是对NIO中derectBuffer的封装;DynamicChannelBuffer继承于AbstractChannelBuffer,实现了buffer的自动扩容;CompositeChannelBuffer也是继承于AbstractChannelBuffer,抽象了操作多个buffer的情况,将多个buffer有序的放入数组中,通过计算找出要操作的buffer的下标,而不是将多个buffer复制到一个更大的buffer中;实现WrappedChannelBuffer接口的类主要是对buffer进行进一步的包装,一般由netty框架内部调用;ReplayingDecoderBuffer用于封装了解码时常有的处理,配合ReplayDecoder使用,后面会对编码解码做专门研究。
ChannelBuffer往往由BufferFactory或者ChannelBuffers类来创建实例。
回到本实例,netty在读取到客户端的msg时,根据用户配置的BufferFactory的不同会将消息封装成derectBuffer或者heapBuffer。所以,当我们在接收数据时,pipline中第一个upstreamHandler拿到的msg(e.getMessage())虽然是Object类型,但是肯定是这两种形式的buffer。那么,我们在写回数据的时候能不能直接使用其他类型呢,答案是:pipline中第一个downstreamHandler不能随意的放入其他对象,原因是pipline中downstream事件是从tail端往上执行的,所以第一个downstreamHandler调用的channel.write()或者channels.write()方法传入的object会直接传递给netty底层处理,而在netty的写入出口中,只接收两种类型的对象:ChannelBuffer和FileRegion(FileRegion可用于0-copy的文件传输,一般情况下,应用程序向socket发送文件流时,操作系统需要先将文件的字节流存储到内核缓冲区(file_read_buffer),然后拷贝到用户缓冲区,在由用户缓冲区拷贝到内核缓冲区(socket_buffer)由协议引擎发送。这样会创建四个缓冲区,两次复制的过程。所谓零拷贝是指:内核通过DMA引擎,直接将file_read_buffer的数据copy到socket_buffer中,而在后面的改进中废除了复制的操作,给socket_buffer增加了数据的位置和长度信息描述,直接将数据从file_read_buffer传递给协议引擎。在netty中只有选择NIO模型才能支持0-copy,当然JDK版本或者操作系统不支持也是不行的)。所以本实例先将字符串”success”方法放入到ChannelBuffer中,然后在调用write方法。
Channels为我们提供了很多方法用于向上或者向下传递事件,对应于概念篇中讲到的各种事件。其中以fire开头方法用于upstream事件,而例如write这样的方法主要用于downstream事件。而这些方法往往是成对出现的,例如:fireChannelOpen(Channel channel)、fireChannelOpen(ChannelHandlerContext ctx)等,由于这两个方法有不同的参数,造成了流程的不同,如果参数是Channel,则整个流程会从pipline的head upstreamHandler开始重新执行,如果参数是ChannelHandlerContext,则会直接执行下一个upstreamHandler。同样,write(Channel channel, Object message)、write(ChannelHandlerContext ctx, ChannelFuture future, Object message)等方法,出了多了一个future外,和前面的两个方法相同,流程也会相同,如果没有传入ChannelFuture,则会在方法中的第一步中创建一个future来支持netty的异步事件处理机制。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值