本文参考尚硅谷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:
零拷贝
零拷贝底层有两种实现,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 缓冲区)。
原文:https://www.cnblogs.com/wangid3/p/14119286.html