2015-05-03 39 views
5

Widzę dziwne zachowanie podczas przesyłania dużych plików z pliku do gniazda przy użyciu zerowej kopii w Javie. Moje środowiska:FileChannel zero-copy transferTo nie może skopiować bajtów do SocketChannel

  • Windows 7 64-bitowy JDK 1.6.0_45 i 1.7.0_79.
  • Centos 6.6 64-bit JDK 1.6.0_35

Co robi program: klient kopie pliku wejściowego do gniazda, i kopie serwera gniazda do pliku wyjściowego z zastosowaniem metody zero-Copy: transferFrom i transferTo. Nie wszystkie bajty docierają do serwera, jeśli rozmiar pliku jest stosunkowo duży, 100 MB w przypadku systemu Windows i 2 GB + w przypadku CentOS. Klient i serwer znajdują się na tym samym komputerze, a do przesyłania danych używany jest adres localhost.

Zachowanie różni się w zależności od systemu operacyjnego. W systemie Windows klient pomyślnie wykonuje metodę transferTo. Liczba przesłanych bajtów jest równa wielkości pliku wejściowego.

long bytesTransferred = fileChannel.transferTo(0, inputFile.length(), socketChannel);

Serwer z drugiej strony, donosi mniejszą liczbę odebranych bajtów.

long transferFromByteCount = fileChannel.transferFrom(socketChannel, 0, inputFile.length());

Na Linuksie bytesTransferred na kliencie jest 2Gb nawet jeśli rozmiar pliku wejściowego jest 4Gb. Na obu konfiguracjach jest wystarczająco dużo miejsca.

W systemie Windows udało mi się przesłać plik 130 MB z jednym z następujących obejść: 1) zwiększanie rozmiaru bufora odbioru na serwerze i 2) dodawanie metody wątku wątku w kliencie. Prowadzi mnie to do wniosku, że metoda transferTo na kliencie kończy się, gdy wszystkie bajty są wysyłane do bufora gniazda, a nie do serwera. To, czy bajty te trafią na serwer, nie jest gwarantowane, co stwarza problemy w moim przypadku użycia.

Maksymalny rozmiar pliku linuksowego, który mogę przesłać za pomocą pojedynczego transferu. Inwokacja to 2 GB, jednak przynajmniej klient zgłasza poprawną liczbę bajtów wysłanych na serwer.

Moje pytania: jaki jest najlepszy sposób, aby klient zagwarantował dostarczenie pliku na serwer, między platformami? Jakie mechanizmy są używane do emulacji sendfile() w systemie Windows?

Oto kod:

Client - ZeroCopyClient.java:

import org.apache.commons.io.FileUtils; 

import java.io.*; 
import java.net.*; 
import java.nio.channels.*; 

public class ZeroCopyClient { 

    public static void main(String[] args) throws IOException, InterruptedException { 

     final File inputFile = new File(args[0]); 

     FileInputStream fileInputStream = new FileInputStream(inputFile); 
     FileChannel fileChannel = fileInputStream.getChannel(); 
     SocketAddress socketAddress = new InetSocketAddress("localhost", 8083); 
     SocketChannel socketChannel = SocketChannel.open(); 
     socketChannel.connect(socketAddress); 

     System.out.println("sending " + inputFile.length() + " bytes to " + socketChannel); 

     long startTime = System.currentTimeMillis(); 
     long totalBytesTransferred = 0; 
     while (totalBytesTransferred < inputFile.length()) { 
      long st = System.currentTimeMillis(); 
      long bytesTransferred = fileChannel.transferTo(totalBytesTransferred, inputFile.length()-totalBytesTransferred, socketChannel); 
      totalBytesTransferred += bytesTransferred; 
      long et = System.currentTimeMillis(); 
      System.out.println("sent " + bytesTransferred + " out of " + inputFile.length() + " in " + (et-st) + " millis"); 
     } 

     socketChannel.finishConnect(); 
     long endTime = System.currentTimeMillis(); 

     System.out.println("sent: totalBytesTransferred= " + totalBytesTransferred + "/" + inputFile.length() + " in " + (endTime-startTime) + " millis"); 

     final File outputFile = new File(inputFile.getAbsolutePath() + ".out"); 
     boolean copyEqual = FileUtils.contentEquals(inputFile, outputFile); 
     System.out.println("copyEqual= " + copyEqual); 

     if (args.length > 1) { 
      System.out.println("sleep: " + args[1] + " millis"); 
      Thread.sleep(Long.parseLong(args[1])); 
     } 
    } 
} 

Server - ZeroCopyServer.java:

import java.io.*; 
import java.net.*; 
import java.nio.channels.*; 
import org.apache.commons.io.FileUtils; 

public class ZeroCopyServer { 

    public static void main(String[] args) throws IOException { 

     final File inputFile = new File(args[0]); 
     inputFile.delete(); 
     final File outputFile = new File(inputFile.getAbsolutePath() + ".out"); 
     outputFile.delete(); 

     createTempFile(inputFile, Long.parseLong(args[1])*1024L*1024L); 

     System.out.println("input file length: " + inputFile.length() + " : output file.exists= " + outputFile.exists()); 

     ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 
     serverSocketChannel.socket().setReceiveBufferSize(8*1024*1024); 
     System.out.println("server receive buffer size: " + serverSocketChannel.socket().getReceiveBufferSize()); 
     serverSocketChannel.socket().bind(new InetSocketAddress("localhost", 8083)); 
     System.out.println("waiting for connection"); 
     SocketChannel socketChannel = serverSocketChannel.accept(); 
     System.out.println("connected. client channel: " + socketChannel); 

     FileOutputStream fileOutputStream = new FileOutputStream(outputFile); 
     FileChannel fileChannel = fileOutputStream.getChannel(); 
     long startTime = System.currentTimeMillis(); 
     long transferFromByteCount = fileChannel.transferFrom(socketChannel, 0, inputFile.length()); 
     long endTime = System.currentTimeMillis(); 
     System.out.println("received: transferFromByteCount= " + transferFromByteCount + " : outputFile= " + outputFile.length() + " : inputFile= " + inputFile.length() + " bytes in " + (endTime-startTime) + " millis"); 

     boolean copyEqual = FileUtils.contentEquals(inputFile, outputFile); 
     System.out.println("copyEqual= " + copyEqual); 

     serverSocketChannel.close(); 

    } 

    private static void createTempFile(File file, long size) throws IOException{ 
     RandomAccessFile f = new RandomAccessFile(file.getAbsolutePath(), "rw"); 
     f.setLength(size); 
     f.writeDouble(Math.random()); 
     f.close(); 
    } 

} 

UPDATE 1: Kod Linux stałe z pętli.

AKTUALIZACJA 2: Jedno możliwe obejście, które rozważam, wymaga współpracy klient-serwer. Po zakończeniu transmisji serwer zapisuje długość odebranych danych do klienta, który klient odczytuje w trybie blokowania.

Serwer odpowiada:

ByteBuffer response = ByteBuffer.allocate(8); 
response.putLong(transferFromByteCount); 
response.flip(); 
socketChannel.write(response); 
serverSocketChannel.close(); 

Bloki klienta z Odczyt:

ByteBuffer response = ByteBuffer.allocate(8); 
socketChannel.read(response); 
response.flip(); 
long totalBytesReceived = response.getLong(); 

W efekcie klient czeka na bajty przejść przez wysyłanie i odbieranie bufory gniazd, aw rzeczywistości czeka bajtów, które zostaną zapisane w pliku wyjściowym. Nie ma potrzeby implementowania potwierdzeń pozapasmowych, a klient nie musi czekać zgodnie z sugestią zawartą w sekcji II.A https://linuxnetworkstack.files.wordpress.com/2013/03/paper.pdf w przypadku, gdy zawartość pliku jest zmienna.

"czekaj«odpowiedniej»czasu, po przepisaniu sam część pliku"

UPDATE 3:

przykładu zawierający poprawki modyfikowane przez @EJP i @ the8472 , z weryfikacją sumy i sumy kontrolnej pliku, bez śledzenia wyników. Zauważ, że obliczenie sumy kontrolnej CRC32 dla dużego pliku może potrwać kilka sekund.

Klient:

import java.io.*; 
import java.net.*; 
import java.nio.*; 
import java.nio.channels.*; 
import org.apache.commons.io.FileUtils; 

public class ZeroCopyClient { 

    public static void main(String[] args) throws IOException { 

     final File inputFile = new File(args[0]); 

     FileInputStream fileInputStream = new FileInputStream(inputFile); 
     FileChannel fileChannel = fileInputStream.getChannel(); 
     SocketAddress socketAddress = new InetSocketAddress("localhost", 8083); 
     SocketChannel socketChannel = SocketChannel.open(); 
     socketChannel.connect(socketAddress); 

     //send input file length and CRC32 checksum to server 
     long checksumCRC32 = FileUtils.checksumCRC32(inputFile); 
     ByteBuffer request = ByteBuffer.allocate(16); 
     request.putLong(inputFile.length()); 
     request.putLong(checksumCRC32); 
     request.flip(); 
     socketChannel.write(request); 

     long totalBytesTransferred = 0; 
     while (totalBytesTransferred < inputFile.length()) { 
      long bytesTransferred = fileChannel.transferTo(totalBytesTransferred, inputFile.length()-totalBytesTransferred, socketChannel); 
      totalBytesTransferred += bytesTransferred; 
     } 

     //receive output file length and CRC32 checksum from server 
     ByteBuffer response = ByteBuffer.allocate(16); 
     socketChannel.read(response); 
     response.flip(); 
     long totalBytesReceived = response.getLong(); 
     long outChecksumCRC32 = response.getLong(); 

     socketChannel.finishConnect(); 

     System.out.println("CRC32 equal= " + (checksumCRC32 == outChecksumCRC32)); 

    } 
} 

Serwer:

import java.io.*; 
import java.net.*; 
import java.nio.*; 
import java.nio.channels.*; 
import org.apache.commons.io.FileUtils; 

public class ZeroCopyServer { 

    public static void main(String[] args) throws IOException { 

     final File outputFile = new File(args[0]); 

     ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 
     serverSocketChannel.socket().bind(new InetSocketAddress(8083));  
     SocketChannel socketChannel = serverSocketChannel.accept(); 

     //read input file length and CRC32 checksum sent by client 
     ByteBuffer request = ByteBuffer.allocate(16); 
     socketChannel.read(request); 
     request.flip(); 
     long length = request.getLong(); 
     long checksumCRC32 = request.getLong(); 

     FileOutputStream fileOutputStream = new FileOutputStream(outputFile); 
     FileChannel fileChannel = fileOutputStream.getChannel(); 
     long totalBytesTransferFrom = 0; 
     while (totalBytesTransferFrom < length) { 
      long transferFromByteCount = fileChannel.transferFrom(socketChannel, totalBytesTransferFrom, length-totalBytesTransferFrom); 
      if (transferFromByteCount <= 0){ 
       break; 
      } 
      totalBytesTransferFrom += transferFromByteCount; 
     } 

     long outChecksumCRC32 = FileUtils.checksumCRC32(outputFile); 

     //write output file length and CRC32 checksum back to client 
     ByteBuffer response = ByteBuffer.allocate(16); 
     response.putLong(totalBytesTransferFrom); 
     response.putLong(outChecksumCRC32); 
     response.flip(); 
     socketChannel.write(response); 

     serverSocketChannel.close(); 

     System.out.println("CRC32 equal= " + (checksumCRC32 == outChecksumCRC32)); 

    } 
} 
+1

'transferTo()' jest nie podano do uzupełnij cały transfer w jednym wywołaniu. Musisz zapętlić. – EJP

+0

@EJP bytesTransferred na kliencie zwraca tę samą liczbę bajtów jako długość pliku wejściowego w systemie Windows, więc nie ma pozostałych bajtów. Myślę, że widzę, że bytesTransferred zwraca, gdy bajty są umieszczane w buforze wysyłania, a nie kiedy są wysyłane na serwer. –

+0

Dodano pętlę klienta zgodnie z sugestią @EJP –

Odpowiedz

3

Rozwiązaniem jest sprawdzenie licznika zapisu z fileChannel.transferFrom:

import java.io.*; 
import java.net.*; 
import java.nio.*; 
import java.nio.channels.*; 
import org.apache.commons.io.FileUtils; 

public class ZeroCopyServer { 

public static void main(String[] args) throws IOException { 

    final File outputFile = new File(args[0]); 

    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 
    serverSocketChannel.socket().bind(new InetSocketAddress(8083));  
    SocketChannel socketChannel = serverSocketChannel.accept(); 

    //read input file length and CRC32 checksum sent by client 
    ByteBuffer request = ByteBuffer.allocate(16); 
    socketChannel.read(request); 
    request.flip(); 
    long length = request.getLong(); 
    long checksumCRC32 = request.getLong(); 

    FileOutputStream fileOutputStream = new FileOutputStream(outputFile); 
    FileChannel fileChannel = fileOutputStream.getChannel(); 
    long totalBytesTransferFrom = 0; 
    while (totalBytesTransferFrom < length) { 
     long transferFromByteCount = fileChannel.transferFrom(socketChannel, totalBytesTransferFrom, length-totalBytesTransferFrom); 
     if (transferFromByteCount <= 0){ 
      break; 
     } 
     totalBytesTransferFrom += transferFromByteCount; 
    } 

    long outChecksumCRC32 = FileUtils.checksumCRC32(outputFile); 

    //write output file length and CRC32 checksum back to client 
    ByteBuffer response = ByteBuffer.allocate(16); 
    response.putLong(totalBytesTransferFrom); 
    response.putLong(outChecksumCRC32); 
    response.flip(); 
    socketChannel.write(response); 

    serverSocketChannel.close(); 

    System.out.println("CRC32 equal= " + (checksumCRC32 == outChecksumCRC32)); 

    } 
}