Netty入门学习(三)-零拷贝

1.前言

此篇博文主要把使用Netty需要了解的知识进行整理并根据自己的理解加以补充。由于内容较多所以将笔记分为多个部分,同时更新速度可能会比较慢,希望读者多包涵。

2.零拷贝

2.1零拷贝基本介绍

  1. 零拷贝是一种I/O操作优化技术,可以快速高效地将数据从文件系统移动到网络接口,不需要将其从内核空间复制到用户空间。
  2. Java 常用的零拷贝技术有mmap(内存映射)、sendFile
    传统I/O读写实例

2.2.传统I/O读写实例

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.3.传统I/O模型

在这里插入图片描述
DMA:direct memory access 直接内存拷贝

2.4.零拷贝技术原理

  1. 基本介绍:零拷贝技术主要是用于处理操作系统在处理I/O操作,频繁的数据复制(主要是不产生CPU复制)
  2. 主要技术:mmap、sendFIle,splice等几种方式。

2.4.1mmap优化

  1. 通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。这样,在进行网络传输时,就可以减少内核空间到用户空间的拷贝次数。
  2. mmap示意图
    在这里插入图片描述

2.4.2.sendFile优化

  1. Linux2.1 版本提供了 sendFile 函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到 SocketBuffer,同时,由于和用户态完全无关,就减少了一次上下文切换
  2. Linux在2.4 版本中,做了一些修改,避免了从内核缓冲区拷贝到 Socketbuffer 的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝。
     图Linux在2.1 版本
     图Linux在2.4 版本

2.4.3.splice方式

  1. splicesendFile非常类似,用户的应用程序必须拥有两个文件描述符,一个表示输入设备另一个表示输出设备。
  2. 与sendFile不同的是,splice允许任意一个文件之间互相连接,这是splice一直使用的机制。
  3. 在Linux2.6.17版本中引入了splice,而在2.6.23的版本中,sendFile机制实现已经没有了。
  4. 与sendFile不同的是splice不需要硬件支持

2.4.4.mmap 和 sendFile 的区别

  1. mmap 适合小数据量读写sendFile 适合大文件传输
  2. mmap 需要 4 次上下文切换,3 次数据拷贝;sendFile需要 3 次上下文切换,最少 2 次数据拷贝。
  3. sendFile 可以利用 DMA 方式,减少 CPU 拷贝,mmap 则不能(必须从内核拷贝到 Socket缓冲区)。

2.4.5.零拷贝理解

  1. 我们说零拷贝,是从操作系统的角度来说的。因为内核缓冲区之间,没有数据是重复的(只有 kernel buffer 有一份数据)。
  2. 零拷贝不仅仅带来更少的数据复制,还能带来其他的性能优势,例如更少的上下文切换,更少的 CPU 缓存伪共享以及无 CPU 校验和计算。
  3. 所谓零拷贝都是为了减少CPU copy 和上下文切换

2.5.各种零拷贝技术对比

CPU拷贝DMA拷贝系统调用上下文切换
传统I/022read/write4
mmap12mmap/write4
sendFile12sendFile2
sendFile with dma scatter/gather copy02sendFile2
splice02splice2

2.6.Java 零拷贝实现

2.6.1基本介绍

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

2.6.2.传统方案

/**
 * 传统I/o拷贝文件
 */
public static void bioCopyFileClient() throws IOException {
    Socket client = new Socket("127.0.0.1",12345);
    OutputStream outputStream = client.getOutputStream();
    FileInputStream fileOutputStream = new FileInputStream("C:\\Users\\admin\\Desktop\\fsdownload\\degLog.zip");
    long startTime = System.currentTimeMillis();
    byte[] bytes = new byte[1024];
    int len ;
    while( (len = fileOutputStream.read(bytes))!=-1){
        outputStream.write(bytes,0,len);
    }
    System.out.println("发送的总的字节数 = " + fileOutputStream.getChannel().size() + " 耗时: " + (System.currentTimeMillis() - startTime));
    client.close();
    outputStream.close();
    fileOutputStream.close();

}
/**
 * 传统I/o拷贝文件
 */
public static void bioCopyFile() throws IOException {
    ExecutorService executorService = new ThreadPoolExecutor(4,8,0L, TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>());
    ServerSocket serverSocket = new ServerSocket(12345);
    System.out.println("服务启动了");
    while (true){
        Socket socket = serverSocket.accept();
        System.out.println("有客户端连接了");
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                byte[] bytes = new byte[1024];
                try {
                    while (true){
                        InputStream inputStream = socket.getInputStream();
                        int read = inputStream.read(bytes);
                        if(read!=-1){
                            System.out.println(new String(bytes,0,read));
                        }else {
                            break;
                        }

                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("服务端关闭");
                }
            }
        });
    }

}

2.6.3.零拷贝方案

/**
 * 零拷贝文件
 */
public static void zeroCopyFile() throws IOException {
    InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8888);
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    ServerSocket socket = serverSocketChannel.socket();
    serverSocketChannel.bind(inetSocketAddress);
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    while(true){
        int read = 0;
        SocketChannel socketChannel = serverSocketChannel.accept();
        while (read!=-1) {
            try {
                read = socketChannel.read(byteBuffer);
            }catch (Exception e){
                break;
            }

        }
        byteBuffer.rewind();

    }
}

/**
 * 零拷贝文件
 */
public static void zeroCopyFileClient() throws IOException {
    SocketChannel  socketChannel = SocketChannel.open();
    socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
    FileChannel fileChannel = new FileInputStream("C:\\Users\\admin\\Desktop\\fsdownload\\degLog.zip").getChannel();
    //准备发送
    long startTime = System.currentTimeMillis();
    long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
    System.out.println("发送的总的字节数 = " + transferCount + " 耗时: " + (System.currentTimeMillis() - startTime));
}

传统方案:发送的总的字节数 = 183977 耗时: 110
零拷贝方案:发送的总的字节数 = 183977 耗时: 10

2.7.Java AIO 基本介绍

  1. 从Java 1.7开始,Java 提供AIO这是异步AIO,Java AIO也称为NIO2.0,提供异步I/O的方式。
  2. Java AIO采用“订阅-通知”模式。
  3. Java AIO也是由操作系统通知的,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用
  4. AIO也是位于java.nio包下

3.BIO、NIO、AIO 对比总结

项目BIONIOAIO
I/O模型同步阻塞异步阻塞异步非阻塞
吞吐量
可靠性
编程难度简单复杂复杂
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值