java nio filechannel socketchannel

Java NIO: transferFrom hasta el final de la secuencia



filechannel socketchannel (7)

supuestamente super eficiente FileChannel.transferFrom.

Si desea obtener los beneficios del acceso a DMA y la IO sin bloqueo, la mejor manera es mapear el archivo en memoria y luego leerlo desde el zócalo a los buffers asignados a la memoria.

Pero eso requiere que preasigne el archivo.

Estoy jugando con la biblioteca NIO. Estoy intentando escuchar una conexión en el puerto 8888 y una vez que se acepta la conexión, somefile todo desde ese canal a somefile .

Sé cómo hacerlo con ByteBuffers , pero me gustaría que funcione con el supuestamente eficiente FileChannel.transferFrom .

Esto es lo que conseguí:

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

Entonces, mi pregunta es: ¿Cómo expreso " transferFrom algún canal hasta que se llegue al final de la transmisión" ?

Edición: Se modificó 1024 a BUF_SIZE, ya que el tamaño del búfer utilizado, es irrelevante para la pregunta.


De esta manera:

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


Hay pocas formas de manejar el caso. Alguna información de fondo sobre cómo se implementa internamente trasnferTo / From y cuándo puede ser superior.

  • Primero y más importante, debes saber cuántos bytes tienes que xferir, es decir, usar FileChannel.size() para determinar el máximo disponible y sumar el resultado. El caso se refiere a FileChannel.trasnferTo(socketChanel)
  • El método no devuelve -1.
  • El método es emulado en Windows. Windows no tiene una función de API que se pueda definir desde el descriptor de archivos al socket, sí que tiene uno (dos) a la vez que el archivo designado por nombre, pero eso es incompatible con la API de Java.
  • En Linux se usa el archivo de sendfile estándar (o sendfile64), en Solaris se llama sendfilev64 .

en resumen for (long xferBytes=0; startPos + xferBytes<fchannel.size();) doXfer() funcionará para la transferencia desde el archivo -> socket. No hay una función del sistema operativo que se transfiera de socket a archivo (en lo que está interesado el OP). Dado que los datos de socket no están en la memoria caché del sistema operativo, no se puede hacer con tanta eficacia, se emula. La mejor manera de implementar la copia es a través de un bucle estándar utilizando un ByteBuffer de tamaño directo con el búfer de lectura de socket. Ya que solo uso IO no bloqueante que implica un selector también.

Dicho esto: me gustaría que funcionara con el supuestamente súper eficiente "? - no es eficiente y se emula en todos los sistemas operativos, por lo que terminará la transferencia cuando el zócalo se cierre con gracia o no. La función ni siquiera lanzar la IOException heredada, siempre que haya CUALQUIER transferencia (si el socket era legible y abierto).

Espero que la respuesta sea clara: el único uso interesante de File.transferFrom ocurre cuando la fuente es un archivo. El más eficiente (y el caso más interesante) es file-> socket y file-> file se implementa a través de filechanel.map / unmap(!!) .


No estoy seguro, pero el JavaDoc dice:

Se intenta leer hasta contar los bytes del canal de origen y escribirlos en el archivo de este canal comenzando en la posición dada. Una invocación de este método puede o no transferir todos los bytes solicitados; Si lo hace o no, depende de la naturaleza y los estados de los canales. Se transferirá menos de la cantidad solicitada de bytes si el canal de origen tiene menos que los bytes de conteo restantes, o si el canal de origen no está bloqueado y tiene menos bytes de conteo disponibles inmediatamente en su búfer de entrada.

Creo que puedes decir que decirle que copie infinitos bytes (por supuesto, no en un bucle) hará el trabajo:

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

Entonces, supongo que cuando la conexión de socket se cierre, el estado cambiará, lo que detendrá el método transferFrom .

Pero como ya dije: no estoy seguro.


Respondiendo su pregunta directamente:

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

Pero si esto es lo que haces, no usas ningún beneficio de no bloquear la IO porque en realidad lo usas exactamente como bloqueando la IO. El punto de no bloqueo IO es que 1 hilo de red puede servir a varios clientes simultáneamente: si no hay nada que leer de un canal (es decir, count == 0 ), puede cambiar a otro canal (que pertenece a otra conexión de cliente).

Por lo tanto, el bucle debería en realidad iterar diferentes canales en lugar de leer desde un canal hasta que termine.

Eche un vistazo a este tutorial: http://rox-xmlrpc.sourceforge.net/niotut/ Creo que le ayudará a comprender el problema.


Sobre la base de lo que otras personas aquí han escrito, aquí hay un método simple de ayuda que logra el objetivo:

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


transferFrom() devuelve un conteo. Solo sigue llamando, avanzando la posición / desplazamiento, hasta que vuelva a cero. Pero comience con un conteo mucho mayor que 1024, más como un megabyte o dos, de lo contrario, no está obteniendo mucho beneficio de este método.

EDITAR Para abordar todos los comentarios a continuación, la documentación dice que "Se transferirá menos del número de bytes solicitado si el canal de origen tiene menos que los bytes de conteo restantes, o si el canal de origen no es de bloqueo y tiene menos bytes de conteo inmediatamente disponible en su búfer de entrada ". Por lo tanto, siempre que estés en modo de bloqueo, no volverá a cero hasta que no quede nada en la fuente. Así que hacer un bucle hasta que devuelva cero es válido.

Editar 2

Los métodos de transferencia son ciertamente mal diseñados. Deberían haber sido diseñados para devolver -1 al final de la transmisión, como todos los métodos read() .