Подтвердить что ты не робот

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

4b9b3361

Ответ 1

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

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

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

Ответ 6

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

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

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

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

Ответ 7

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

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