NIO与零拷贝的示例分析
这期内容当中小编将会给大家带来有关NIO与零拷贝的示例分析,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。
创新互联建站是一家专注于做网站、网站建设与策划设计,依安网站建设哪家好?创新互联建站做网站,专注于网站建设十余年,网设计领域的专业建站公司;建站业务涵盖:依安等地区。依安做网站价格咨询:13518219792
一、NIO是什么
先来看如下一段代码:
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(8888).accept();
socket.getOutputStream().write(arr);
这段代码就是读取一个文件,然后再把它写出去,看起来就几行代码,其实涉及到多次拷贝,其流程如下:
读取需要拷贝的数据,这个过程有两个步骤:首先将文件通过DMA(direct memory access,直接内存拷贝,不经过CPU) 拷贝到系统内核的buffer中,然后在内核buffer中通过 CPU拷贝到用户的buffer中,这才完成了读取文件的步骤。即前四行就发生了两次拷贝。
写数据的时候,将数据从用户buffer通过CPU拷贝到socket buffer中,最后从socket buffer通过DMA拷贝到协议栈。即最后一行也发生了两次拷贝。
整个过程,发生了四次拷贝,三次状态的切换。从一开始的用户态,切换到内核态,再切换到用户态,最后再切换成内核态。一次简单的读写,就有这么多名堂,性能肯定是不好的,所有就出现了零拷贝,零拷贝,不是不拷贝,而是整个过程不需要进行CPU拷贝。
二、零拷贝
1、使用mmap优化上述流程:mmap,是指通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据,这样,在进行网络传输时,就可以减少内核空间到用户空间的拷贝次数。同样做上面的事情,使用mmap时整个过程如下:
首先通过DMA拷贝将硬盘数据拷贝到内核buffer,但是因为用户buffer可以共享内核buffer的数据,所以步骤二的cpu拷贝就免了;
然后是直接从内核buffer通过CPU拷贝到socket buffer,最后DMA拷贝到协议栈。
整个过程三次拷贝,三次状态的切换,相比传统拷贝,优化了一丢丢,但这并不是零拷贝。
2、使用sendFile优化:linux 2.1的sendFile:sendFile是linux2.1版本开始提供的一个函数,可以让文件直接从内核buffer进入到socket buffer,不需要经过用户态,过程如下:
首先还是将数据从硬盘中通过DMA拷贝到内核buffer,然后通过CPU拷贝将数据从内核buffer拷贝到socket buffer,最后通过DMA拷贝到协议栈。
整个过程还是3次拷贝,但是减少了一次装态切换,从用户态到内核态再到用户态,只经过了两次切换。这里还是有一次CPU拷贝,还不是真正的零拷贝。
linux 2.4的sendFile:linux 2.4对sendFile又做了一些优化,首先还是DMA拷贝到内核buffer,然后再通过CPU拷贝到socket buffer,最后DMA拷贝到协议栈。优化的点就在于,这次的CPU拷贝,拷贝的内容很少,只拷贝内核buffer的长度、偏移量等信息,消耗很低,可以忽略。因此,这个就是零拷贝。NIO的transferTo
方法就可以实现零拷贝。
三、案例代码
1、传统IO拷贝大文件:
服务端:接收客户端发来的数据
public class OldIoServer {
@SuppressWarnings("resource")
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(6666);
while (true) {
Socket socket = serverSocket.accept();
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
byte[] byteArray = new byte[4096];
while (true) {
int readCount = dataInputStream.read(byteArray, 0, byteArray.length);
if (-1 == readCount) {
break;
}
}
}
}
}
客户端:发送一个大文件到服务端:
public class OldIoClient {
@SuppressWarnings("resource")
public static void main(String[] args) throws Exception {
Socket socket = new Socket("127.0.0.1", 6666);
// 需要拷贝的文件
String fileName = "E:\\download\\soft\\windows\\jdk-8u171-windows-x64.exe";
InputStream inputStream = new FileInputStream(fileName);
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
byte[] buffer = new byte[4096];
long readCount;
long total = 0;
long start = System.currentTimeMillis();
while ((readCount = inputStream.read(buffer)) >= 0) {
total += readCount;
dataOutputStream.write(buffer);
}
long end = System.currentTimeMillis();
System.out.println("传输总字节数:" + total + ",耗时:" + (end - start) + "毫秒");
dataOutputStream.close();
inputStream.close();
socket.close();
}
}
这里拷贝了一个JDK,最后运行结果如下:
传输总字节数:217342912,耗时:4803毫秒
可以看到,将近5秒钟。接下来看看使用NIO的transferTo
方法耗时情况:
服务端:
public class NioServer {
public static void main(String[] args) throws IOException {
InetSocketAddress address = new InetSocketAddress(6666);
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(address);
ByteBuffer buffer = ByteBuffer.allocate(4096);
while (true) {
SocketChannel socketChannel = serverSocketChannel.accept();
int readCount = 0;
while (-1 != readCount) {
readCount = socketChannel.read(buffer);
buffer.rewind(); // 倒带,将position设置为0,mark设置为-1
}
}
}
}
客户端:
public class NioClient {
@SuppressWarnings("resource")
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", 6666));
String fileName = "E:\\download\\soft\\windows\\jdk-8u171-windows-x64.exe";
FileChannel channel = new FileInputStream(fileName).getChannel();
long start = System.currentTimeMillis();
// 在linux下,transferTo方法可以一次性发送数据
// 在windows中,transferTo方法传输的文件超过8M得分段
long totalSize = channel.size();
long transferTotal = 0;
long position = 0;
long count = 8 * 1024 * 1024;
if (totalSize > count) {
BigDecimal totalCount = new BigDecimal(totalSize).divide(new BigDecimal(count)).setScale(0, RoundingMode.UP);
for (int i=1; i<=totalCount.intValue(); i++) {
if (i == totalCount.intValue()) {
transferTotal += channel.transferTo(position, totalSize, socketChannel);
} else {
transferTotal += channel.transferTo(position, count + position, socketChannel);
position = position + count;
}
}
} else {
transferTotal += channel.transferTo(position, totalSize, socketChannel);
}
long end = System.currentTimeMillis();
System.out.println("发送的总字节:" + transferTotal + ",耗时:" + (end - start) + "毫秒");
channel.close();
socketChannel.close();
}
}
客户端发送文件调用transferTo
方法要注意,在window中,这个方法一次只能传输8M,超过8M的文件要分段,像代码中那样分段传输,在linux中是没这个限制的。运行后结果如下:
发送的总字节:217342912,耗时:415毫秒
从结果可以看到,BIO与NIO耗时相差一个数量级,NIO只要0.4s,而BIO要4s。所以在网络传输中,使用NIO的零拷贝,可以大大提高性能。
上述就是小编为大家分享的NIO与零拷贝的示例分析了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注创新互联行业资讯频道。
文章题目:NIO与零拷贝的示例分析
网页网址:http://scyanting.com/article/ppgsos.html