Java NIO: TransferFrom до конца потока

Я играю с библиотекой 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, поскольку размер используемого буфера не имеет отношения к вопросу.


person aioobe    schedule 04.10.2011    source источник


Ответы (7)


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

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

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

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

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

person bestsss    schedule 08.05.2012
comment
Как только соединение будет принято, выгрузите все с этого канала в какой-нибудь файл. Таким образом, ввод представляет собой канал сокета, поэтому он не может знать, сколько в нем данных, если только отправитель сначала не отправит слово длины. Я не знаю, что использовать FileChannel.size() для определения максимально доступного и суммирования результата. - person user207421; 08.05.2012
comment
@EJP - читайте дальше, он сообщает, что файл socket -› бесполезен и эмулируется. Использование выделенного прямого ByteBuffer размером с буфер чтения сокета - способ выполнить задачу. 1024 — очень плохой размер для чтения (выделенная память всегда > pageSize [4k], поэтому 1024 просто тратит память впустую и вызывает больше обращений ядра). Маркеры впереди показывают, как работает метод transfer внутри. - person bestsss; 08.05.2012
comment
Конечно, но первая часть вашего ответа о файловом сокете не имеет отношения к вопросу ОП. - person user207421; 08.05.2012
comment
@EJP, файл-›сокет — единственная действительно полезная передача, поэтому я поставил ее на первое место. файл->файл почти в порядке, но включает в себя mmap/munmap, а последняя является дорогостоящей операцией, очищающей TLB всех ЦП (что отстойно для многопроцессорных, поэтому вызов должен быть выполнен с максимально возможной длиной, т.е. это может иметь даже негативное влияние на общую производительность). Хорошо, я отредактирую, чтобы показать, что маркеры предоставляют информацию о методах xfer. :) - person bestsss; 08.05.2012
comment
Это все не по теме. Пользователь указал свои требования. - person user207421; 08.05.2012

Прямо отвечая на ваш вопрос:

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

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

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

Взгляните на это руководство: http://rox-xmlrpc.sourceforge.net/niotut/ Думаю, это поможет вам разобраться в этом вопросе.

person AlexR    schedule 04.10.2011
comment
Но вызов может вернуть 0 байтов, не достигнув конца потока, верно? - person aioobe; 04.10.2011
comment
@aioobe Только (а) в неблокирующем режиме, когда вы все равно должны использовать селектор, а не цикл, или (б) если длина буфера равна нулю, что является ошибкой программирования. - person user207421; 06.05.2012

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

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

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

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

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

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

person Martijn Courteaux    schedule 04.10.2011
comment
Ты подтолкнул меня на это. Однако вы можете использовать Long.MAX_VALUE вместо Integer.MAX_VALUE. - person Kohányi Róbert; 04.10.2011
comment
Верно, но что, если я получу только 3 байта из 10 при первом вызове transferFrom? - person aioobe; 04.10.2011
comment
Возможно, проверьте свой исходный канал: у него есть int read(ByteBuffer). Если это возвращает -1, то в нем ничего нет, поэтому вы перенесли все в FileChannel. Если вам нужно что-то более надежное, то вам гораздо лучше использовать ByteBuffers (повторное использование в любом случае довольно эффективно). - person Kohányi Róbert; 04.10.2011
comment
А что, если он не возвращает -1? Затем я использовал несколько байтов... Это полностью портит код. - person aioobe; 04.10.2011
comment
В документации ReadableByteChannel указано, что метод int read(ByteBuffer) возвращает The number of bytes read, possibly zero, or -1 if the channel has reached end-of-stream. Итак, если он действительно возвращает -1, то вы достигли конца потока. Что должно указывать... на конец потока? Если вы не можете на это положиться, то как вы собираетесь переносить все с канала куда-то еще? Помимо этого, вы написали, что вы know how to do it with ByteBuffers. Если вы знаете это, то как в таком случае вы решаете, можете ли вы перестать читать с канала? - person Kohányi Róbert; 04.10.2011
comment
Да ладно, я невнимательно прочитал ваш последний комментарий: вы правы. Но последняя часть моего последнего комментария остается в силе. :) - person Kohányi Róbert; 04.10.2011
comment
Конечно. Я согласен, что возврат к read решит проблему выяснения того, достигнут ли конец потока... Если только это не окажется единственным способом, это не то, что мне нужно... - person aioobe; 04.10.2011

якобы сверхэффективный FileChannel.transferFrom.

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

Но это требует, чтобы вы предварительно выделили файл.

person the8472    schedule 06.05.2012

Сюда:

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 ;)
}
person Romain-p    schedule 06.06.2015

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

EDIT Чтобы ответить на все комментарии ниже, в документации говорится, что «меньше запрошенного количества байт будет передано, если в исходном канале осталось меньше count байтов или если исходный канал не блокируется. и имеет меньше, чем count байтов, непосредственно доступных во входном буфере». Поэтому, если вы находитесь в режиме блокировки, он не вернет ноль, пока в источнике ничего не останется. Таким образом, цикл до тех пор, пока он не вернет ноль, действителен.

ИЗМЕНИТЬ 2

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

person user207421    schedule 05.10.2011
comment
В документации метода сказано, что он возвращает The number of bytes, possibly zero, that were actually transferred. Это не означает, что он не может вернуться с 0 сразу после первого вызова, ничего не передав. (Другой вопрос: когда и зачем это делать?) Однако ОП не может на это полагаться. Я начинаю думать, что единственный способ узнать наверняка, что этот метод передает все из канала в файл, это знать заранее, сколько байт будет передано. - person Kohányi Róbert; 05.10.2011
comment
Это уже обсуждалось в разделе комментариев для этого ответа. Однако ОП хочет почти однострочное решение (я могу относиться к этому, потому что это было бы здорово). - person Kohányi Róbert; 05.10.2011
comment
@EJP, у вас есть какой-либо аргумент или хорошая ссылка на тот факт, что возврат к обычному read (и, возможно, необходимость записи такого байта вручную) действительно является лучшим решением этой проблемы? - person aioobe; 05.10.2011
comment
@aioobe Никто не говорил, что это лучшее решение. При исследовании источника для SocketChannel JVM выполняет передачу сама, поэтому этот метод не имеет смысла для SocketChannels. К моему удивлению, JVM слишком сильно реализует это как между двумя FileChannels (ссылка JDK 1.6 src/share/classes/sun/nio/ch/FileChannelImpl.java), поэтому заявление Javadoc о передаче [кольцовых] байтов ОС напрямую.. ... без их фактического копирования" является "неработоспособным". - person user207421; 08.10.2011
comment
@KohányiRóbert Вы можете сделать это в две строки кода, если не возражаете опустить фигурные скобки. Вы не можете использовать transferTo() или transferFrom() в одной строке, что бы ни захотел OP. - person user207421; 06.05.2012
comment
Возможно, понижение связано с тем, что было бы неправильно вызывать transferFrom до тех пор, пока он не вернет 0, если намерение состоит в том, чтобы истощить поток (как указал @KohányiRóbert). Если кто-то не читает комментарии, Просто продолжайте вызывать его, продвигая позицию/смещение, пока он не вернет ноль. на самом деле вводит в заблуждение. - person aioobe; 06.05.2012
comment
@aioobe В этом нет ничего плохого, смотрите мое редактирование. Большая часть комментариев выше направлена ​​не по адресу. - person user207421; 08.05.2012
comment
@KohányiRóbert Что касается вашего первого комментария выше, это не означает, что он не может вернуться с 0, в режиме блокировки это означает именно это. - person user207421; 08.05.2012
comment
Я сделал то же самое вчера и считаю, что это правильный способ сделать это. Кроме того, такая реализация позволяет сообщать о статусе передачи, что может пригодиться. - person rpvilao; 20.02.2014

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

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