Java NIO: от конца до конца потока - PullRequest
16 голосов
/ 04 октября 2011

Я играю с библиотекой NIO.Я пытаюсь прослушать соединение через порт 8888, и как только соединение будет принято, выгрузите все с этого канала на somefile.

Я знаю, как это сделать с ByteBuffers, но я быкак заставить его работать с якобы супер эффективным FileChannel.transferFrom.

Вот что я получил:

ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.socket().bind(new InetSocketAddress(8888));

SocketChannel sChannel = ssChannel.accept();
FileChannel out = new FileOutputStream("somefile").getChannel();

while (... sChannel has not reached the end of the stream ...)     <-- what to put here?
    out.transferFrom(sChannel, out.position(), BUF_SIZE);

out.close();

Итак, мой вопрос: как мневыразить "transferFrom некоторый канал до достижения конца потока" ?


Редактировать: 1024 изменено на BUF_SIZE, поскольку размер используемого буферане имеет значения для вопроса.

Ответы [ 7 ]

11 голосов
/ 08 мая 2012

Есть несколько способов справиться с делом. Некоторая справочная информация о том, как trasnferTo / From реализован внутри и когда он может быть лучше.

  • Во-первых, вы должны знать, сколько байтов вам нужно обработать, т. Е. Используйте FileChannel.size(), чтобы определить максимально доступный максимум и суммировать результат. Дело относится к FileChannel.trasnferTo(socketChanel)
  • Метод не возвращает -1
  • Метод эмулируется в Windows. В Windows нет функции API для переноса из файлового дескриптора в сокет, у нее есть один (два) для переноса из файла, обозначенного именем, но это несовместимо с API Java.
  • В Linux используется стандартный sendfile (или sendfile64), в Solaris он называется sendfilev64.

короче for (long xferBytes=0; startPos + xferBytes<fchannel.size();) doXfer() будет работать для передачи из файла -> сокет. Не существует функции ОС, которая переносит данные из сокета в файл (который интересует OP). Так как данные сокета не находятся в кэше ОС, это не может быть сделано так эффективно, они эмулируются Лучший способ реализовать копию - через стандартный цикл с использованием прямого опрашиваемого байтового буфера с размером буфера чтения сокета. Поскольку я использую только неблокирующий ввод-вывод, который также включает селектор.

Как говорится: Я бы хотел, чтобы он работал с якобы суперэффективным "? - он неэффективен и эмулируется на всех ОС, следовательно, он завершит передачу, когда сокет закрыто изящно или нет. Функция даже не сгенерирует унаследованное IOException, если произошла ЛЮБАЯ передача (если сокет был читаем и открыт).

Надеюсь, ответ ясен: единственное интересное использование File.transferFrom происходит, когда источником является файл. Наиболее эффективным (и интересным случаем) является файл-> сокет, а файл-> файл реализуется через filechanel.map / unmap (!!) .

4 голосов
/ 04 октября 2011

Отвечая на ваш вопрос напрямую:

while( (count = socketChannel.read(this.readBuffer) )  >= 0) {
   /// do something
}

Но если это то, что вы делаете, вы не используете никаких преимуществ неблокирующего ввода-вывода, потому что вы фактически используете его именно как блокирующий ввод-вывод. Смысл неблокирующего ввода-вывода состоит в том, что 1 сетевой поток может обслуживать несколько клиентов одновременно: если нечего читать с одного канала (например, count == 0), вы можете переключиться на другой канал (который принадлежит другому клиентскому соединению). *

Таким образом, цикл должен фактически повторять разные каналы вместо чтения из одного канала, пока он не закончится.

Посмотрите этот урок: http://rox -xmlrpc.sourceforge.net / niotut / Я верю, что это поможет вам понять проблему.

4 голосов
/ 04 октября 2011

Я не уверен, но JavaDoc говорит:

попытка выполняется для считывания байтов из исходного канала и запишите их в файл этого канала, начиная с заданной позиции. Вызов этого метода может или не может передать все запрошенные байты; или нет, зависит от природы и состояния каналов. Меньше, чем запрошенное количество байтов будет передан, если исходный канал имеет меньше, чем число байтов оставшийся, или если исходный канал не является блокирующим и имеет меньше чем количество байтов, немедленно доступных в его входном буфере.

Я думаю, вы можете сказать, что указание скопировать бесконечные байты (конечно, не в цикле) сделает работу:

out.transferFrom(sChannel, out.position(), Integer.MAX_VALUE);

Итак, я думаю, что когда сокетное соединение закрыто, состояние изменится, что остановит метод transferFrom.

Но, как я уже сказал: я не уверен.

1 голос
/ 07 июня 2015

Таким образом:

URLConnection connection = new URL("target").openConnection();
File file = new File(connection.getURL().getPath().substring(1));
FileChannel download = new FileOutputStream(file).getChannel();

while(download.transferFrom(Channels.newChannel(connection.getInputStream()),
        file.length(), 1024) > 0) {
    //Some calculs to get current speed ;)
}
1 голос
/ 07 мая 2012

предположительно супер эффективный FileChannel.transferFrom.

Если вы хотите использовать преимущества доступа к DMA и неблокирующего ввода-вывода, лучший способ - отобразить файл в памяти и затем просто прочитать изсокет в буферы, отображаемые в память.

Но для этого необходимо предварительно выделить файл.

1 голос
/ 05 октября 2011

transferFrom() возвращает счет. Просто продолжайте вызывать его, продвигая позицию / смещение, пока он не вернет ноль. Но начните с гораздо большего счета, чем 1024, больше как мегабайт или два, иначе вы не получите большой выгоды от этого метода.

РЕДАКТИРОВАТЬ Чтобы обратиться ко всем комментариям ниже, в документации говорится, что «будет передано меньше запрошенного количества байтов, если у исходного канала осталось меньше, чем число байтов, или если исходный канал не -блокирование и имеет меньше, чем количество байтов, сразу же доступных в своем входном буфере. " Поэтому, если вы находитесь в режиме блокировки, он не вернет ноль, пока в источнике ничего не останется. Так что цикл, пока он не вернет ноль, действителен.

РЕДАКТИРОВАТЬ 2

Методы передачи, конечно, неправильно спроектированы. Они должны были быть рассчитаны на возврат -1 в конце потока, как и все методы read().

0 голосов
/ 25 января 2018

Основываясь на том, что написали здесь другие люди, вот простой вспомогательный метод, который достигает цели:

public static void transferFully(FileChannel fileChannel, ReadableByteChannel sourceChannel, long totalSize) {
    for (long bytesWritten = 0; bytesWritten < totalSize;) {
        bytesWritten += fileChannel.transferFrom(sourceChannel, bytesWritten, totalSize - bytesWritten);
    }
}
...