• Java基础之《netty(7)—NIO与零拷贝》


    一、零拷贝基本介绍

    1、零拷贝是网络编程的关键,很多性能优化都离不开。

    2、在Java程序中,常用的零拷贝有mmap(内存映射)和sendFile。在OS里,他们是怎样的一个设计?

    3、NIO中如何使用零拷贝。

    二、传统IO数据读写

    1、Java传统IO和网络编程的一段代码

    File file = new File("test.txt");
    RandomAccessFile raf = new RandomAccessFile(file, "rw");
    byte[] arr = new byte[(int)file.length()];
    //把文件数据读入到字节数组中
    raf.read(arr);
    Socket socket = new ServerSocket(8080).accept();
    socket.getOutputStream().write(arr);

    2、DMA
    direct memory access,直接内存拷贝(不使用CPU)

    传统IO:
    Hard drive -> kernal buffer -> user buffer -> socket buffer -> protocol engine
    经过了4次拷贝,3次切换。

    3、mmap优化
    mmap通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。这样在进行网络传输时,就可以减少内核空间到用户空间的拷贝次数。

    4、sendFile优化
    Linux2.1版本提供了sendFile函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到Socket Buffer,同时,由于和用户态完全无关,就减少了一次上下文切换。

    5、我们说的零拷贝是从操作系统的角度看的,是没有cpu拷贝。

    三、零拷贝再次理解

    1、我们说零拷贝,是从操作系统的角度来说的。因为内核缓冲区之间,没有数据是重复的(只有kernel buffer有一份数据)。

    2、零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,例如更少的上下文切换,更少的CPU缓存伪共享以及无CPU校验和计算。

    四、mmap和sendFile的区别

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

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

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

    五、NIO零拷贝案例

    1、使用传统的IO方法传递一个大文件
    2、使用NIO零拷贝方式传递(transferTo)一个大文件
    3、看看两种传递方式耗时时间分别是多少
    4、传统IO
    OldIOServer.java

    1. package netty.zerocopy;
    2. import java.io.DataInputStream;
    3. import java.net.ServerSocket;
    4. import java.net.Socket;
    5. public class OldIOServer {
    6. public static void main(String[] args) throws Exception {
    7. ServerSocket serverSocket = new ServerSocket(7001);
    8. while(true) {
    9. Socket socket = serverSocket.accept();
    10. DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
    11. try {
    12. byte[] byteArray = new byte[4096];
    13. while(true) {
    14. int readCount = dataInputStream.read(byteArray, 0, byteArray.length);
    15. if (-1 == readCount) {
    16. break;
    17. }
    18. }
    19. } catch (Exception ex) {
    20. ex.printStackTrace();
    21. }
    22. }
    23. }
    24. }

    OldIOClient.java

    1. package netty.zerocopy;
    2. import java.io.DataOutputStream;
    3. import java.io.FileInputStream;
    4. import java.io.InputStream;
    5. import java.net.Socket;
    6. public class OldIOClient {
    7. public static void main(String[] args) throws Exception {
    8. Socket socket = new Socket("localhost", 7001);
    9. String fileName = "d:\\aaa.zip";
    10. InputStream inputStream = new FileInputStream(fileName);
    11. DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
    12. byte[] buffer = new byte[4096];
    13. long readCount;
    14. long total = 0;
    15. long startTime = System.currentTimeMillis();
    16. while((readCount = inputStream.read(buffer)) >= 0) {
    17. total += readCount;
    18. dataOutputStream.write(buffer);
    19. }
    20. System.out.println("发送总字节数:" + total + ",耗时:" + (System.currentTimeMillis() - startTime));
    21. inputStream.close();
    22. socket.close();
    23. }
    24. }

    5、零拷贝
    NewIOServer.java

    1. package netty.zerocopy;
    2. import java.net.InetSocketAddress;
    3. import java.net.ServerSocket;
    4. import java.net.SocketAddress;
    5. import java.nio.ByteBuffer;
    6. import java.nio.channels.ServerSocketChannel;
    7. import java.nio.channels.SocketChannel;
    8. public class NewIOServer {
    9. public static void main(String[] args) throws Exception {
    10. SocketAddress socketAddress = new InetSocketAddress(7001);
    11. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    12. ServerSocket serverSocket = serverSocketChannel.socket();
    13. serverSocket.bind(socketAddress);
    14. //创建buffer
    15. ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
    16. while(true) {
    17. SocketChannel socketChannel = serverSocketChannel.accept();
    18. int readCount = 0;
    19. while(readCount != -1) {
    20. try {
    21. //从socketChannel读取数据到byteBuffer
    22. readCount = socketChannel.read(byteBuffer);
    23. } catch (Exception ex) {
    24. ex.printStackTrace();
    25. }
    26. //将buffer倒带
    27. byteBuffer.rewind();
    28. }
    29. }
    30. }
    31. }

    NewIOClient.java

    1. package netty.zerocopy;
    2. import java.io.FileInputStream;
    3. import java.net.InetSocketAddress;
    4. import java.nio.channels.FileChannel;
    5. import java.nio.channels.SocketChannel;
    6. public class NewIOClient {
    7. public static void main(String[] args) throws Exception {
    8. SocketChannel socketChannel = SocketChannel.open();
    9. socketChannel.connect(new InetSocketAddress("localhost", 7001));
    10. String fileName = "d:\\aaa.zip";
    11. //得到一个文件的channel
    12. FileChannel fileChannel = new FileInputStream(fileName).getChannel();
    13. //准备发送
    14. long startTime = System.currentTimeMillis();
    15. //在linux下一个transferTo方法可以完成传输
    16. //在windows下,一次调用transferTo只能发送8MB的文件,就需要分段传输文件,而且要注意传输时的位置
    17. //transferTo底层使用到零拷贝
    18. long start = 0; //起始位置
    19. long size = fileChannel.size(); //文件大小
    20. long transCount = 0; //传输了多少
    21. long sum = 0;
    22. while (true) {
    23. transCount = fileChannel.transferTo(start, size, socketChannel);
    24. sum += transCount;
    25. if (transCount < size) {
    26. start += transCount;
    27. size -= transCount;
    28. } else {
    29. break;
    30. }
    31. }
    32. //long transCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
    33. System.out.println("发送总字节数:" + sum + ",耗时:" + (System.currentTimeMillis() - startTime));
    34. //关闭
    35. fileChannel.close();
    36. socketChannel.close();
    37. }
    38. }

    6、耗时
    传统IO耗时:
    发送总字节数:51182677,耗时:245
    零拷贝耗时:
    发送总字节数:51182677,耗时:42
     

  • 相关阅读:
    5个超好用的视频素材网站,视频剪辑必备。
    数据结构-作业7
    概率论基础
    大数据Hadoop入门教程 | (二)Linux
    Airtext连接chrome谷歌浏览器报错
    Reading Note(7)——AutoDSE
    数据清洗与规范化详解
    【图像分类】2021-DeiT
    WSL VScode连接文件后无法修改(修改报错)
    Kubernetes:kube-scheduler 源码分析
  • 原文地址:https://blog.csdn.net/csj50/article/details/128194061