2015-05-03 6 views
5

Я вижу странное поведение при передаче больших файлов из файла в сокет с использованием нулевой копии на Java. Мое окружение:Передача нулевой копии FileChannel Не удается скопировать байты в SocketChannel

  • Windows 7 64-бит JDK 1.6.0_45 и 1.7.0_79.
  • Centos 6,6 64-битный JDK 1.6.0_35

Что делает программа: клиент копирует входной файл в розетку, и сервер копирует сокет в выходной файл, используя методы нулевого копирования: transferFrom и о переходе. Не все байты достигают сервера, если размер файла относительно велик, 100 Мб + в случае Windows и 2 ГБ + в случае Centos. Клиент и сервер находятся на одном компьютере, а адрес локального адреса используется для передачи данных.

Поведение различно в зависимости от ОС. В Windows клиент успешно завершает метод transferTo. Количество переданных байтов равно размеру входного файла.

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

Сервер с другой стороны, передает меньшее количество принятых байтов.

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

В Linux bytesTransferred на клиенте 2Gb, даже если размер входного файла 4Gb. Для обеих конфигураций достаточно места.

В Windows я смог перенести файл 130 МБ с помощью одного из следующих способов: 1) увеличение размера буфера приема на сервере и 2) добавление метода слияния потока в клиенте. Это заставляет меня думать, что метод transferTo на клиенте завершается, когда все байты отправляются в буфер отправки сокетов, а не на сервер. Независимо от того, передаются ли эти байты на сервер, не гарантируется ли это, что создает проблемы для моего варианта использования.

Максимальный размер файла Linux, который я могу передать с помощью одной передачи для вызова, составляет 2 ГБ, однако, по крайней мере, клиент сообщает правильное количество байтов, отправленных на сервер.

Мои вопросы: что является лучшим способом для клиента обеспечить гарантированную доставку файла на сервер, кросс-платформенный? Какие механизмы используются для эмуляции sendfile() в Windows?

Вот код:

Клиент - 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])); 
     } 
    } 
} 

Сервер - 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 код фиксируется с петлей.

ОБНОВЛЕНИЕ 2: Возможное обходное решение, которое я рассматриваю, требует взаимодействия клиент-сервер. По завершении передачи сервер записывает длину полученных данных обратно клиенту, который клиент считывает в режиме блокировки.

Сервер отвечает:

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

Клиентские блоки чтения:

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

В результате, клиент ожидает байты, чтобы пройти через отправку и получение буферов сокета, а на самом деле жду для получения байтов в выходном файле. Нет необходимости реализовывать внеполосные подтверждения, и клиенту также не нужно ждать, как было предложено в разделе II.A. https://linuxnetworkstack.files.wordpress.com/2013/03/paper.pdf в случае, если содержимое файла изменено.

"ждать„соответствующий“количество времени, прежде чем переписывать же часть файла"

UPDATE 3:

Модифицированный пример, включающий исправления по @EJP и @ the8472 , с проверкой контрольной суммы длины и файла, без трассировки вывода. Обратите внимание, что для вычисления контрольной суммы CRC32 для большого файла может потребоваться несколько секунд.

Клиент:

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

    } 
} 

Сервер:

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

'о переходе()' не указано в завершить весь перенос за один раз. Вы должны зациклиться. – EJP

+0

@EJP bytesTransferred on client возвращает тот же самый байт, что и длина входного файла в Windows, поэтому оставшихся байтов нет. Я думаю, что я вижу, что bytesTransferred возвращается, когда байты помещаются в буфер отправки, а не когда они отправляются на сервер. –

+0

Добавлен клиентский цикл, предложенный @EJP –

ответ

3

Решение состоит в том, чтобы проверить счетчик записи из 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)); 

    } 
} 

 Смежные вопросы

  • Нет связанных вопросов^_^