Netty框架之IO零拷贝

Posted 木兮君

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Netty框架之IO零拷贝相关的知识,希望对你有一定的参考价值。

前言

小编在netty章节也陆陆续续分享了自己的理解,netty章节即将完毕,不过可能大家比较关心netty的面试题,所以这次分享后,小编总结了一些netty的面试题,也相当于总结了我们的netty文章,netty小编分享的不是特别好,不过大家如果会运用了,那网络编程的能力肯定有很大的提高。
今天小编分享的IO零拷贝从严格意义上来说不一定是netty的,但是还是有必要分享一下。本来小编还想这对protobuf 做一个集成,不过实际用到的却不多,则放弃了。好了进入正题

IO零拷贝

零拷贝之前,先普及一些概念,首先就是用户太和内核态以及他们切换上下文。

用户空间和内核空间(用户态与内核态)

一如既往小编使用图来给大家解释一下:

  1. 上图中内存的用户空间可以相当于java的应用,里面的堆内存,其应用可以快速访问
  2. 内核空间是在应用之外的内存空间,他可以由操作系统来调用。
  3. 假设java应用中,我们要读取(或写到)硬盘中的文件,那他并不是从硬盘中将文件的数据直接读到用户空间。他无法直接操作硬盘,而读取硬盘文件只能由内核空间的指令才可以读取。
  4. 当cpu使用syscall read函数去硬盘进行读取,不过这里并不会让cpu一直占用,而是DMA copy去硬盘中拷贝到内核空间。
  5. 当完成了从硬盘到内核之后,cpu才将硬盘的数据拷贝到堆内存,这样我们的应用才可以拿到数据
  6. 整个拷贝过程 用户空间 -> 内核空间 ->硬盘 -> 内核空间 -> 堆内存 然后用用户空间使用。

假如我们使用bio或nio拷贝的时候,直接从硬盘中读取信息,这都会阻塞,这里大家会不会有疑问,为什么nio明明为同步非阻塞,那为什么像bio一样阻塞呢,其实因为是硬盘,硬盘读取是没有多路复用选择器去进行轮询,但如果从网卡中进行copy那就不一样了。
了解了用户空间以及内核空间后,咱们再来看一下我们io的copy过程(http请求)。

读写请求的io拷贝过程

普通请求过程


普通的网络请求,从请求到响应,首先上下文切换是4次,然后拷贝也是4次,分别为两次DMA拷贝,和两次CPU拷贝。
再此基础上优化。从内核空间直接copy到内核空间。

mmap(memory map 内存映射)


从上图来看,我们的拷贝没有进过用户空间,而是采用了堆外映射,
这样我们的拷贝只有3次,不过上下文切换还是4次。

sendFile

上图通过映射来用cpu copy,也就是说还是进过了用户态,那我们再继续优化,让映射去掉,也就是不进过用户态,看下图:


sendFile是linux2.1之后增加的系统函数,在windows里面为TransmiteFile函数。这样我们就只有三次copy,上下文切换业只有两次了。

sendFile优化

sendFile是linux2.4之后进行了优化,可减少内核之间的copy。如下图

这里的cpu copy相当于做了一次映射,copy了文件的位置,起始位置,大小等信息,这样socket到网卡之间其实是从file的内核空间进行拷贝。进过这次优化,是两次上下文切换两次拷贝,且没有cpu拷贝。这是最后一次优化。

上面的过程就是IO零拷贝的演进过程。

代码实践

看完了理论那我们使用代码来证明实践一下:
首先是java的nio零拷贝

java nio zero copy

public class JavaNioZeroTest {
    @Test
    public void zeroCopyTest(){
        //mmap 直接声明一个堆外内存
        //声明和访问速度慢点
        // 外部数据传输,不需要进行cpu copy
        ByteBuffer directBuffer = ByteBuffer.allocateDirect(10);
        //堆内存 声明和访问速度更快
        //如果要到硬盘里面得进行一次内核态的切换
        ByteBuffer heapBuffer = ByteBuffer.allocate(10);

    }

    @Test
    public void mapTest() throws IOException {
        String file_name = "C:\\\\Users\\\\hasee\\\\Desktop\\\\xxx.docx";
        String copy_name = "C:\\\\Users\\\\hasee\\\\Desktop\\\\xxxcopy.docx";
        FileChannel channel = new RandomAccessFile(file_name, "rw").getChannel();
        FileChannel copyChannel = new RandomAccessFile(copy_name, "rw").getChannel();
        //建立映射
        MappedByteBuffer mapped = channel
                .map(FileChannel.MapMode.READ_WRITE, 0, channel.size());
        long begin = System.nanoTime();
        copyChannel.write(mapped);
        System.out.println((System.nanoTime() - begin) / 1.0e6);
        copyChannel.close();
        channel.close();
    }

    // 零拷贝
    @Test
    public void testZeroCopy() throws IOException {
        String file_name = "xxxx.mp4";
        String copy_name = "xxxcopy.mp4";
        FileChannel channel = new RandomAccessFile(file_name, "rw").getChannel();
        FileChannel copyChannel = new RandomAccessFile(copy_name, "rw").getChannel();
        long begin = System.nanoTime();
//        channel.transferTo(0, channel.size(), copyChannel); // 拷贝到指定目标
        //从批定目标拷贝到当前管道
        copyChannel.transferFrom(channel, 0, channel.size());
        System.out.println((System.nanoTime() - begin) / 1.0e6);
        channel.close();
        copyChannel.close();

    }
}

这边文件如果不大效果是差不多的

netty zero copy

public class NettyZeroTest {



    @Test
    public void testByNetty() {
        //DirectBuffer
        // 声明堆外内存 mmap
        ByteBuf directBuf = Unpooled.directBuffer(1024);
        //声明堆内存
        ByteBuf heapBuf = Unpooled.buffer(1024);
    }
    //mmap
    //sendFile

    @Test
    public void testUploadNyNetty() throws InterruptedException {
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup(8));
        bootstrap.channel(NioserverSocketChannel.class);
        bootstrap.childHandler(new ChannelInitializer<Channel>() {
            @Override
            protected void initChannel(Channel ch) {
                // directBuf
                ch.pipeline().addLast(new Upload());
                // 编码器
                // 编解码 headBuf
            }
        });
        ChannelFuture future = bootstrap.bind(8080);
        System.out.println("启动成功");
        future.sync().channel().closeFuture().sync();
    }


    private class Upload extends SimpleChannelInboundHandler {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println(msg);

            String fileName="xxx.txt";
            RandomAccessFile file=new RandomAccessFile(fileName,"r");
            // sendFile 2次切换 2次拷贝FileRegion
            FileRegion fileRegion=new DefaultFileRegion(file.getChannel(),0,file.length());
            ctx.writeAndFlush(fileRegion);
            
            // mmap 4次切换 3次拷贝
            MappedByteBuffer map = file.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length());
            ByteBuf buf = Unpooled.wrappedBuffer(map);
            ctx.writeAndFlush(buf);
        }
    }
}

这边小编提一嘴,大家什么时候使用headBuffer与directBuffer,buffer的数据不需要编解码,直接进入到服务不需要进行额外处理可以使用directBuffer。否则使用headBuffer。

总结

这次相当于netty的最后一次分享,后面还会有一篇复习博文也可以称为面试题,之后的话小编先分享tomcat,然后进入redis章节,和小编一起学习,争取学习完整个java的体系。加油!

以上是关于Netty框架之IO零拷贝的主要内容,如果未能解决你的问题,请参考以下文章

Netty框架之IO零拷贝

netty之linux零拷贝

netty之linux零拷贝

百万并发「零拷贝」技术系列之经典案例Netty

六.Netty入门到超神系列-Java NIO零拷贝实战

五.Netty入门到超神系列-零拷贝技术