首页 > 其他 > 详细

NIO与零拷贝

时间:2020-12-11 14:31:50      阅读:32      评论:0      收藏:0      [点我收藏+]

本文参考尚硅谷netty

NIO 有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)

Channel通道表示打开到 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。简而言之,Channel 负责传输, Buffer 负责存储。

Selector(选择器):多个Channel以事件的方式可以注册到同一个Selector, Selector 能够检测多个注册的通道上是否有事件发生(用于客户端-服务器网络编程,在本节不必体现),如果发生了,调用服务器上的处理方法,避免服务器线程阻塞。

 

技术分享图片

selector channel buffer之间的关系

 

缓冲区的使用:

Buffer有多种子类,在不同场合使用

技术分享图片

 

 

 

 

 

 

 

 

 

 

 

 

buffer四个重要属性:

技术分享图片

 

buffer的方法:

 

 

 

 技术分享图片

 

 

Channel 表示 IO 源与目标打开的连接。 Channel 类似于传统的“流”。只不过 Channel 本身不能直接访问数据,Channel 只能与 Buffer 进行交互,而且Channel 是双向的,可读可写。

Channel常用方法有:

技术分享图片

 

 

 传统IO:

  1. 调用read()函数,文件数据被copy到内核缓冲区
  2. read()函数返回,文件数据从内核缓冲区copy到用户缓冲区
  3. write()函数调用,将文件数据从用户缓冲区copy到内核与socket相关的缓冲区。
  4. 数据从socket缓冲区copy到相关协议引擎。

技术分享图片

 

零拷贝

零拷贝底层有两种实现,mmap 与 Sendfile

Sendfile基本原理如下:数据根本不 经过用户态,直接从内 核缓冲区进入到 Socket Buffer,同时,由于和用户态完全无关,就减少 了一次上下文切换。后来做了 一些修改,避免了从内核缓冲区拷贝到 Socket buffer的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝。这里其实有 一次cpu 拷贝 kernel buffer -> socket buffer 但是,拷贝的信息很少,比如 lenght , offset , 消耗低,可以忽略。

技术分享图片

 

transferTo()方法基于sendfile实现零拷贝

 //通道之间的数据传输(直接缓冲区)
    @Test
    public void test3(){
        long start=System.currentTimeMillis();

        FileChannel inChannel=null;
        FileChannel outChannel=null;
        try {
            inChannel = FileChannel.open(Paths.get("d:/1.avi"), StandardOpenOption.READ);
            outChannel=FileChannel.open(Paths.get("d:/5.avi"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);

            inChannel.transferTo(0, inChannel.size(), outChannel);
            //outChannel.transferFrom(inChannel, 0, inChannel.size());
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(outChannel!=null){
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(inChannel!=null){
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        long end=System.currentTimeMillis();
        System.out.println("耗费的时间为:"+(end-start));//耗费的时间为:147
    }

 

直接字节缓冲区还可以通过 FileChannel 的 map() 方法 将文件区域直接映射到内存中来创建。该方法返回 MappedByteBuffer 。Java 平台的实现有助于通过 JNI 从本机代码创建直接字节缓冲区。如果以上这些缓冲区 中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在 访问期间或稍后的某个时间导致抛出不确定的异常。 ? 字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其 isDirect() 方法来确定。提供此方法是为了能够在 性能关键型代码中执行显式缓冲区管理。

技术分享图片

 //使用直接缓冲区完成文件的复制(内存映射文件)
    @Test
    public void test2() {
        long start=System.currentTimeMillis();

        FileChannel inChannel=null;
        FileChannel outChannel=null;
        try {
            inChannel = FileChannel.open(Paths.get("d:/1.avi"), StandardOpenOption.READ);
            outChannel=FileChannel.open(Paths.get("d:/3.avi"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);

            //内存映射文件
            MappedByteBuffer inMappedBuf=inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
            MappedByteBuffer outMappedBuf=outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
            //直接对缓冲区进行数据的读写操作
            byte[] dst=new byte[inMappedBuf.limit()];
            inMappedBuf.get(dst);
            outMappedBuf.put(dst);
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(outChannel!=null){
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(inChannel!=null){
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        long end=System.currentTimeMillis();
        System.out.println("耗费的时间为:"+(end-start));//耗费的时间为:200
    }

网络编程中的常用的零拷贝allocateDirect()的底层mmap的实现。

mmap 和 sendFile 的区别

1) mmap 适合小数据量读写,sendFile 适合大文件传输。

2) mmap 需要 4 次上下文切换,3 次数据拷贝;sendFile 需要 3 次上下文切换,最少 2 次数据拷贝。

3) sendFile 可以利用 DMA 方式,减少 CPU 拷贝,mmap 则不能(必须从内核拷贝到Socket 缓冲区)。

 

NIO与零拷贝

原文:https://www.cnblogs.com/wangid3/p/14119286.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!