而接收端接收到的都是bytebuf,然后我们接收端可以将其写到filechannel中。这样在接收端就可以写成文件了,从这样看我们的内存也不会因为传输的文件大而爆掉。因为我们底层是依靠transferTo的transferTo去循环发送文件数据
Netty 传输文件的时候没有使用 ByteBuf 进行向 Channel 中写入数据,而使用的 FileRegion。下面通过示例了解下 FileRegion 的用法,然后深入源码分析 为什么不使用 ByteBuf 而使用 FileRegion。
从示例中可以看出 ChannelPipeline 中添加了自定义的 FileServerHandler()。
下面看下 FileServerHandler 的源码,其它几个 Handler 的都是 Netty 中自带的,以后会分析这些 Handler 的具体实现原理。
从 FileServerHandler 中可以看出,传输文件使用了 DefaultFileRegion 进行写入到 NioSocketChannel 里。
我们知道向 NioSocketChannel 里写数据,都是使用的 ByteBuf 进行写入。这里为啥使用 DefaultFileRegion 呢?
DefaultFileRegion 中有一个很重要的方法 transferTo() 方法
这里可以看出 文件 通过 FileChannel.transferTo 方法直接发送到 WritableByteChannel 中。
通过 Nio 的 FileChannel 可以使用 map 文件映射的方式,直接发送到 SocketChannel中,这样可以减少两次 IO 的复制。
第一次 IO:读取文件的时间从系统内存中拷贝到 jvm 内存中。
第二次 IO:从 jvm 内存中写入 Socket 时,再 Copy 到系统内存中。
这就是所谓的零拷贝技术。
从 ChannelOutboundBuffer 中获取 FileRegion 类型的节点。
然后调用 NioSocketChannel.doWriteFileRegion() 方法进行写入。
这里调用 FileRegion.transferTo() 方法,使用 基于文件内存映射技术进行文件发送。
netty的零拷贝技术主要基于以下几点:
1. 堆外内存,也叫直接内存
2. Composite Buffers
3. 文件传输基于linux的sendfile机制
Linux的设计的初衷:给不同的 *** 作给与不同的“权限”。Linux *** 作系统就将权限等级分为了2个等级,分别就是 内核态和用户态。
内核态是属于cpu的特权工作模式,可以 *** 作计算机设备中的任何元件,包括网卡、硬盘、内存等等。
用户态是应用程序的工作模式,只能 *** 作已申请的内存空间,无法 *** 作外围设备。当应用程序需要与网卡、硬盘等外围设备进行交互时,需要通过系统提供的接口,来调用外围设备。
堆内存中的数据如果需要发送到外围设备,需要调用系统的接口,将数据拷贝到堆外内存中,发送到外围设备中。
而Netty的ByteBuffer不经过堆内存,直接在堆外内存中进行读写,省去一步拷贝 *** 作。
需要注意的是,堆外内存只能通过主动调用回收或者Full GC回收,如果使用不当,容易造成内存溢出。
Composite Buffers
Netty提供了Composite Buffers来组合多个buffer。传统的buffer如果要合并的话,需要新建一个buffer,将原来的buffer拷贝到新的buffer中进行合并。而Composite Buffers相当于buffer的集合,保存了每个buffer对象,使物理的buffer合并变为逻辑上的buffer的合并。
文件传输
Netty的文件传输是依赖于 *** 作系统的零拷贝技术。
一般我们读取文件都是调用 *** 作系统接口, *** 作系统在应用程序读取文件时,会首先判断文件是否在内核缓冲区中,如果不在,则需要将文件从磁盘或socket读取到内核缓冲区中。
在写入文件时, *** 作系统会将文件先写入内核缓冲区,再写入到socket中。
我们传统做文件拷贝或传输时,会先在应用程序内存中构建一个缓冲区,通过这个缓冲区与 *** 作系统做数据交换。这样无疑会增加了文件的多次拷贝。
传统的文件传输过程如下:
1. 构建byte[]数组来缓冲文件
2. 切换到内核态,将文件先在内核缓冲区中缓存
3. 将内核缓冲区的数据拷贝到应用程序缓冲区的byte[]数组中
4. 切换回用户态
5. 执行写入 *** 作,切换回内核态
6. 将数据再拷贝一份到内核中的socket缓冲区
7. 切换回用户态
8. *** 作系统将数据异步刷新到网卡
传统的文件传输过程,会造成 *** 作系统在用户态和内核态多次切换,非常影响性能。
而linux在内核2.1中引入了sendfile *** 作,过程如下:
1. 读取数据时,sendfile系统调用导致文件内容通过DMA模块被复制到内核缓冲区中
2. 写入数据时,数据直接复制到socket关联的缓冲区(linux内核2.4已删除这一步,取而代之的是,只有记录数据位置和长度的描述符被加入到socket缓冲区中。DMA模块将数据直接从内核缓冲区传递给协议引擎)
3. 最后将socket buffer中的数据copy到网卡设备中(protocol buffer)发送
netty的FileRegion 包下的FileChannel.tranferTo即是基于sendfile机制来实现文件传输的
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)