Java-第十七部分-NIO和Netty-零拷贝

208 阅读2分钟

NIO和Netty全文

零拷贝

  • 零拷贝,从操作系统角度看,没有CPU拷贝,在内核缓冲区中,数据是没有重复的,更少的上下文切换
  • java中常用的零拷贝 mmap 内存映射sendFile
  • DMA,direct memory access,直接内存拷贝,不使用cpu
  • 传统IO传输,四次拷贝,四次状态切换 image.png

mmap优化

  • 通过内存映射将文件映射到内核缓冲区,用户空间共享内核空间的数据,在网络传输时,减少内核空间到用户空间的拷贝次数
  • 三次拷贝,四次状态切换 image.png

sendFile优化

  • 数据不经过用户态,直接从内存缓冲区进入Socket Buffer,与用户态完全无关,减少状态切换
  • 三次拷贝,两次状态切换 image.png

sendFile进一步优化

  • 大部分数据从内存缓冲区直接拷贝到协议栈
  • 小部分数据需要拷贝到Socket Buffer,如length,offset,信息量少,消耗低
  • 两次拷贝,两次状态切换 image.png

mmap和sendFile

  • mmap适合小数据量读写,sendFile适合大文件传输
  • sendFile利用DMA减少CPU拷贝,mmap不能,必须从内核拷贝到Socket缓冲区

NIO中的零拷贝

传统

  • 客户端
Socket s = new Socket("localhost", 7001);
String fileName = "/Users/mzx/Desktop/java/WechatIMG1022.jpg";
FileInputStream fis = new FileInputStream(fileName);
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
byte[] bytes = new byte[4096];
long readCount;
long total = 0;
long st = System.currentTimeMillis();
while ((readCount = fis.read(bytes)) >= 0) {
    total += readCount;
    dos.write(bytes);
    System.out.println(total);
}
System.out.println(total + " - " + (System.currentTimeMillis() - st));
dos.close();
s.close();
fis.close();
  • 服务端
ServerSocket ss = new ServerSocket(7001);
while (true) {
    Socket s = ss.accept();
    DataInputStream dis = new DataInputStream(s.getInputStream());
    byte[] bytes = new byte[4096];
    int len = 0;
    while ((len = dis.read(bytes, 0, len)) != -1) {

    }
}

NIO

  • 利用transferTo
  • 客户端
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("localhost", 7002));
String fileName = "/Users/mzx/Desktop/java/WechatIMG1022.jpg";
FileChannel fc = new FileInputStream(fileName).getChannel();
long st = System.currentTimeMillis();
//win下一次只能发送8m,linux无限制,mac无限制
long len = fc.transferTo(0, fc.size(), sc);
fc.close();
sc.close();
System.out.println(len + " - " + (System.currentTimeMillis() - st));
  • 服务端
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(7002));
ByteBuffer bb = ByteBuffer.allocate(4096);
while (true) {
    SocketChannel sc = ssc.accept();
    int len = 0;
    while ((len = sc.read(bb)) != -1) {
        bb.rewind();
    }
}