2015-05-03 7 views
5

Ich sehe ein seltsames Verhalten bei der Übertragung großer Dateien von Datei zu Socket mit Null-Kopie in Java. Meine Umgebungen:FileChannel zero-copy transferTo kann keine Bytes in SocketChannel kopieren

  • Windows 7 64-Bit-JDK 1.6.0_45 und 1.7.0_79.
  • Centos 6.6 64-Bit-JDK 1.6.0_35

Was das Programm macht: Client kopiert eine Eingabedatei in eine Steckdose, und Server kopiert Buchse Ausgabedatei Null-Kopie Methoden verwenden: transferFrom und transferTo. Nicht alle Bytes erreichen den Server, wenn die Dateigröße relativ groß ist, 100Mb + bei Windows und 2GB + bei Centos. Client und Server befinden sich auf demselben Computer, und die Adresse des lokalen Hosts wird zum Übertragen von Daten verwendet.

Das Verhalten ist je nach Betriebssystem unterschiedlich. Unter Windows führt der Client die Methode "transferTo" erfolgreich aus. Die Anzahl der übertragenen Bytes entspricht der Größe der Eingabedatei.

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

Der Server auf der anderen Seite, berichtet über eine geringere Anzahl von empfangenen Bytes.

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

Unter Linux bytesTransferred auf Client ist 2Gb auch wenn Eingabedateigröße 4 GB ist. In beiden Konfigurationen ist ausreichend Speicherplatz vorhanden.

Unter Windows konnte ich eine 130Mb-Datei mit einer der folgenden Problemumgehungen übertragen: 1) Erhöhung der Empfangspuffergröße auf dem Server und 2) Hinzufügen der Thread-Schlafmethode im Client. Dies führt zu der Annahme, dass die Methode transferTo auf dem Client abgeschlossen ist, wenn alle Bytes an den Socket-Sendepuffer und nicht an den Server gesendet werden. Ob diese Bytes den Server erreichen oder nicht, ist nicht garantiert, was zu Problemen für meinen Anwendungsfall führt.

Unter Linux maximale Dateigröße, die ich mit einer einzigen Übertragung übertragen kann, um Aufruf ist 2 GB, jedoch mindestens der Client meldet eine korrekte Anzahl von Bytes an den Server gesendet.

Meine Fragen: Was ist der beste Weg für den Client, um die garantierte Lieferung der Datei an den Server zu gewährleisten, plattformübergreifend? Welche Mechanismen werden verwendet, um sendfile() unter Windows zu emulieren?

Hier ist der Code:

-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: Linux-Code mit Schleife festgelegt.

UPDATE 2: Eine mögliche Problemumgehung, die ich in Betracht ziehe, erfordert Client-Server-Zusammenarbeit. Am Ende der Übertragung schreibt der Server die Länge der empfangenen Daten zurück auf den Client, die der Client im Blockiermodus liest.

Server antworten:

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

Die Client-Blöcke mit Lese:

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

Im Ergebnis wartet der Client auf das Bytes passieren senden und Socket-Puffer, und in der Tat wartet erhalten um Bytes in der Ausgabedatei zu speichern. Es ist nicht erforderlich, Out-of-Band-Bestätigungen zu implementieren, und der Client muss auch nicht wie in Abschnitt II.A https://linuxnetworkstack.files.wordpress.com/2013/03/paper.pdf vorgeschlagen warten, wenn der Dateiinhalt veränderbar ist.

"warten eine‚geeignete‘Menge an Zeit, bevor den gleichen Teil Datei Neuschreiben"

UPDATE 3:

Ein modifiziertes Beispiel durch fixe @EJP und @ the8472 Einbeziehung , mit Überprüfung sowohl der Länge als auch der Dateiprüfsumme, ohne Ausgabeverfolgung. Beachten Sie, dass das Berechnen der CRC32-Prüfsumme für eine große Datei einige Sekunden dauern kann.

Auftraggeber:

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

    } 
} 

Server:

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()' ist nicht angegeben Vervollständigen Sie die gesamte Übertragung in einem einzigen Anruf. Sie müssen Schleife. – EJP

+0

@EJP bytesTransfered auf dem Client gibt die gleiche Bytezahl wie die Länge von inputFile unter Windows zurück, so dass keine verbleibenden Bytes vorhanden sind. Ich denke, was ich sehe, ist, dass BytesTransfered zurückkehrt, wenn Bytes in Sendepuffer abgelegt werden, nicht wenn sie an den Server gesendet werden. –

+0

Hinzugefügt eine Client-Schleife wie von @ EJP vorgeschlagen –

Antwort

3

Die Lösung ist Schreibzähler von fileChannel.transferFrom zu überprüfen:

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

    } 
}