日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

NIO與零拷貝

 貪挽懶月 2022-06-20 發(fā)布于廣東

一、是什么

先來看如下一段代碼:

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);

這段代碼就是讀取一個文件,然后再把它寫出去,看起來就幾行代碼,其實涉及到多次拷貝,其流程如下:

  • 讀取需要拷貝的數(shù)據(jù),這個過程有兩個步驟:首先將文件通過DMA(direct memory access,直接內(nèi)存拷貝,不經(jīng)過CPU) 拷貝到系統(tǒng)內(nèi)核的buffer中,然后在內(nèi)核buffer中通過 CPU拷貝到用戶的buffer中,這才完成了讀取文件的步驟。即前四行就發(fā)生了兩次拷貝。

  • 寫數(shù)據(jù)的時候,將數(shù)據(jù)從用戶buffer通過CPU拷貝到socket buffer中,最后從socket buffer通過DMA拷貝到協(xié)議棧。即最后一行也發(fā)生了兩次拷貝。

整個過程,發(fā)生了四次拷貝,三次狀態(tài)的切換。從一開始的用戶態(tài),切換到內(nèi)核態(tài),再切換到用戶態(tài),最后再切換成內(nèi)核態(tài)。一次簡單的讀寫,就有這么多名堂,性能肯定是不好的,所有就出現(xiàn)了零拷貝,零拷貝,不是不拷貝,而是整個過程不需要進行CPU拷貝。

二、零拷貝

1、使用mmap優(yōu)化上述流程:mmap,是指通過內(nèi)存映射,將文件映射到內(nèi)核緩沖區(qū),同時,用戶空間可以共享內(nèi)核空間的數(shù)據(jù),這樣,在進行網(wǎng)絡(luò)傳輸時,就可以減少內(nèi)核空間到用戶空間的拷貝次數(shù)。同樣做上面的事情,使用mmap時整個過程如下:

  • 首先通過DMA拷貝將硬盤數(shù)據(jù)拷貝到內(nèi)核buffer,但是因為用戶buffer可以共享內(nèi)核buffer的數(shù)據(jù),所以步驟二的cpu拷貝就免了;

  • 然后是直接從內(nèi)核buffer通過CPU拷貝到socket buffer,最后DMA拷貝到協(xié)議棧。

整個過程三次拷貝,三次狀態(tài)的切換,相比傳統(tǒng)拷貝,優(yōu)化了一丟丟,但這并不是零拷貝。

2、使用sendFile優(yōu)化:linux 2.1的sendFile:sendFile是linux2.1版本開始提供的一個函數(shù),可以讓文件直接從內(nèi)核buffer進入到socket buffer,不需要經(jīng)過用戶態(tài),過程如下:

  • 首先還是將數(shù)據(jù)從硬盤中通過DMA拷貝到內(nèi)核buffer,然后通過CPU拷貝將數(shù)據(jù)從內(nèi)核buffer拷貝到socket buffer,最后通過DMA拷貝到協(xié)議棧。

整個過程還是3次拷貝,但是減少了一次裝態(tài)切換,從用戶態(tài)到內(nèi)核態(tài)再到用戶態(tài),只經(jīng)過了兩次切換。這里還是有一次CPU拷貝,還不是真正的零拷貝。

linux 2.4的sendFile:linux 2.4對sendFile又做了一些優(yōu)化,首先還是DMA拷貝到內(nèi)核buffer,然后再通過CPU拷貝到socket buffer,最后DMA拷貝到協(xié)議棧。優(yōu)化的點就在于,這次的CPU拷貝,拷貝的內(nèi)容很少,只拷貝內(nèi)核buffer的長度、偏移量等信息,消耗很低,可以忽略。因此,這個就是零拷貝。NIO的transferTo方法就可以實現(xiàn)零拷貝。

三、案例代碼

1、傳統(tǒng)IO拷貝大文件:

  • 服務(wù)端:接收客戶端發(fā)來的數(shù)據(jù)
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;
    }
   }
  }
 }
}
  • 客戶端:發(fā)送一個大文件到服務(wù)端:
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("傳輸總字節(jié)數(shù):" + total + ",耗時:" + (end - start) + "毫秒");
  dataOutputStream.close();
  inputStream.close();
  socket.close();
 }
}

這里拷貝了一個JDK,最后運行結(jié)果如下:

傳輸總字節(jié)數(shù):217342912,耗時:4803毫秒

可以看到,將近5秒鐘。接下來看看使用NIO的transferTo方法耗時情況:

  • 服務(wù)端:
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設(shè)置為0,mark設(shè)置為-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方法可以一次性發(fā)送數(shù)據(jù)
        // 在windows中,transferTo方法傳輸?shù)奈募^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("發(fā)送的總字節(jié):" + transferTotal + ",耗時:" + (end - start) + "毫秒");
  channel.close();
  socketChannel.close();
 }
}

客戶端發(fā)送文件調(diào)用transferTo方法要注意,在window中,這個方法一次只能傳輸8M,超過8M的文件要分段,像代碼中那樣分段傳輸,在linux中是沒這個限制的。運行后結(jié)果如下:

發(fā)送的總字節(jié):217342912,耗時:415毫秒

從結(jié)果可以看到,BIO與NIO耗時相差一個數(shù)量級,NIO只要0.4s,而BIO要4s。所以在網(wǎng)絡(luò)傳輸中,使用NIO的零拷貝,可以大大提高性能。

-java開發(fā)那些事-

    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多