前言
小编在netty章节也陆陆续续分享了自己的理解,netty章节即将完毕,不过可能大家比较关心netty的面试题,所以这次分享后,小编总结了一些netty的面试题,也相当于总结了我们的netty文章,netty小编分享的不是特别好,不过大家如果会运用了,那网络编程的能力肯定有很大的提高。
今天小编分享的IO零拷贝从严格意义上来说不一定是netty的,但是还是有必要分享一下。本来小编还想这对protobuf 做一个集成,不过实际用到的却不多,则放弃了。好了进入正题
IO零拷贝
零拷贝之前,先普及一些概念,首先就是用户太和内核态以及他们切换上下文。
用户空间和内核空间(用户态与内核态)
一如既往小编使用图来给大家解释一下:
- 上图中内存的用户空间可以相当于java的应用,里面的堆内存,其应用可以快速访问
- 内核空间是在应用之外的内存空间,他可以由操作系统来调用。
- 假设java应用中,我们要读取(或写到)硬盘中的文件,那他并不是从硬盘中将文件的数据直接读到用户空间。他无法直接操作硬盘,而读取硬盘文件只能由内核空间的指令才可以读取。
- 当cpu使用syscall read函数去硬盘进行读取,不过这里并不会让cpu一直占用,而是DMA copy去硬盘中拷贝到内核空间。
- 当完成了从硬盘到内核之后,cpu才将硬盘的数据拷贝到堆内存,这样我们的应用才可以拿到数据
- 整个拷贝过程 用户空间 -> 内核空间 ->硬盘 -> 内核空间 -> 堆内存 然后用用户空间使用。
假如我们使用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的体系。加油!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/13544.html