• Netty3-NIO与零拷贝


    1.传统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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    传统IO 进行四次拷贝,三次切换(仅仅是读写)

    mmap 优化

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

    sendFile 优化1

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

    所谓零拷贝是操作系统角度看,是没有CPU拷贝

    sendFile 优化

    1. Linux 在 2.4 版本中,做了
      一些修改,避免了从内核
      缓冲区拷贝到 Socket
      buffer 的操作,直接拷贝到
      协议栈,从而再一次减少
      了数据拷贝。

    小结:
    这里其实有 一次cpu 拷贝
    kernel buffer -> socket buffer
    但是,拷贝的信息很少,比如
    lenght , offset , 消耗低,可以
    忽略

    零拷贝的再次理解

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

    零拷贝案例-Server

    package com.atguigu.nio.zerocopy;
    
    import java.net.InetSocketAddress;
    import java.net.ServerSocket;
    import java.nio.ByteBuffer;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    
    //服务器
    public class NewIOServer {
        public static void main(String[] args) throws Exception {
    
            InetSocketAddress address = new InetSocketAddress(7001);
    
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    
            ServerSocket serverSocket = serverSocketChannel.socket();
    
            serverSocket.bind(address);
    
            //创建buffer
            ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
    
            while (true) {
                SocketChannel socketChannel = serverSocketChannel.accept();
    
                int readcount = 0;
                while (-1 != readcount) {
                    try {
    
                        readcount = socketChannel.read(byteBuffer);
    
                    }catch (Exception ex) {
                       // ex.printStackTrace();
                        break;
                    }
                    //
                    byteBuffer.rewind(); //倒带 position = 0 mark 作废
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    零拷贝案例-CLient

    package com.atguigu.nio.zerocopy;
    
    import java.io.FileInputStream;
    import java.net.InetSocketAddress;
    import java.nio.channels.FileChannel;
    import java.nio.channels.SocketChannel;
    
    public class NewIOClient {
        public static void main(String[] args) throws Exception {
    
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.connect(new InetSocketAddress("localhost", 7001));
            String filename = "protoc-3.6.1-win32.zip";
    
            //得到一个文件channel
            FileChannel fileChannel = new FileInputStream(filename).getChannel();
    
            //准备发送
            long startTime = System.currentTimeMillis();
    
            //在linux下一个transferTo 方法就可以完成传输
            //在windows 下 一次调用 transferTo 只能发送8m , 就需要分段传输文件, 而且要主要
            //传输时的位置 =》 课后思考...
            //transferTo 底层使用到零拷贝
            long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
    
            System.out.println("发送的总的字节数 =" + transferCount + " 耗时:" + (System.currentTimeMillis() - startTime));
    
            //关闭
            fileChannel.close();
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    BIO、NIO、AIO对比表

    在这里插入图片描述

    1. 同步阻塞:到理发店理发,就一直等理发师,直到轮到自己理发。
    2. 同步非阻塞:到理发店理发,发现前面有其它人理发,给理发师说下,先干其他事情,一会过来看是否轮到自己.
    3. 异步非阻塞:给理发师打电话,让理发师上门服务,自己干其它事情,理发师自己来家给你理发

    NIO零拷贝关键方法

    将此通道的文件中的字节传输到给定的可写字节通道。
    尝试读取从该通道文件中给定位置开始的count个字节,并将它们写入目标通道。此方法的调用可能会也可能不会传输所有请求的字节;是否这样做取决于通道的性质和状态。如果此通道的文件从给定位置开始包含少于count个字节,或者如果目标通道是非阻塞的并且其输出缓冲区中的空闲字节少于count个,则传输的字节数少于请求的字节数。
    此方法不会修改此通道的位置。如果给定位置大于文件的当前大小,则不传输任何字节。如果目标通道有一个位置,则从该位置开始写入字节,然后该位置增加写入的字节数。
    这种方法可能比从该通道读取并写入目标通道的简单循环更有效。许多操作系统可以直接将字节从文件系统缓存传输到目标通道,而无需实际复制它们。
    参数:
    position – 文件中开始传输的位置;必须是非负数
    count – 要传输的最大字节数;必须是非负数
    target – 目标通道
    返回值:
    实际传输的字节数,可能为零
    抛出:
    IllegalArgumentException – 如果参数的先决条件不成立
    NonReadableChannelException – 如果未打开此通道进行读取
    NonWritableChannelException – 如果没有打开目标通道进行写入
    ClosedChannelException – 如果此通道或目标通道已关闭
    AsynchronousCloseException – 如果另一个线程在传输过程中关闭任一通道
    ClosedByInterruptException – 如果另一个线程在传输过程中中断当前线程,从而关闭两个通道并设置当前线程的中断状态
    IOException – 如果发生其他一些 I/O 错误
     public abstract long transferTo(long position, long count,
                                        WritableByteChannel target)
            throws IOException;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
  • 相关阅读:
    k8s部署gateway、nacos 、app通过网关访问app出现404
    4款.NET开源的Redis客户端驱动库
    几何角度理解线性代数(3):点积与叉积
    计算机专业大学摆烂四年,找份合适的工作其实也很快
    力扣(LeetCode)25. K 个一组翻转链表(C++)
    Nacos作为配置中心
    2022杭电多校3(总结+补题)
    Linux——网络配置
    springboot下使用druid-spring-boot-starter
    Spring Framework 简介与起源
  • 原文地址:https://blog.csdn.net/weixin_47409774/article/details/125425418