Conectando una stream de entrada a una salida de salida

actualización en java9: https://docs.oracle.com/javase/9/docs/api/java/io/InputStream.html#transferTo-java.io.OutputStream-

Vi algunos hilos similares, pero no del todo lo que necesito.

Tengo un servidor, que básicamente tomará la entrada de un cliente, el cliente A, y lo reenviará, byte por byte, a otro cliente, el cliente B.

Me gustaría conectar mi inputstream del cliente A con mi flujo de salida del cliente B. ¿Es posible? ¿Cuáles son las formas de hacer eso?

Además, estos clientes se envían mensajes entre sí, que son algo sensibles al tiempo, por lo que el almacenamiento en memoria intermedia no funcionará. No quiero un buffer de, digamos, 500 y un cliente envía 499 bytes y luego mi servidor se detiene al reenviar los 500 bytes porque no ha recibido el último byte para llenar el buffer.

En este momento, estoy analizando cada mensaje para encontrar su longitud, luego leo bytes de longitud y luego los reenvío. Pensé (y probé) que esto sería mejor que leer un byte y reenviar un byte una y otra vez porque sería muy lento. Tampoco quería usar un búfer o un temporizador por la razón que indiqué en mi último párrafo: no quiero que los mensajes me hagan esperar demasiado tiempo porque el buffer no está lleno.

¿Cuál es una buena manera de hacer esto?

Solo porque uses un buffer no significa que el stream tenga que llenar ese buffer. En otras palabras, esto debería estar bien:

public static void copyStream(InputStream input, OutputStream output) throws IOException { byte[] buffer = new byte[1024]; // Adjust if you want int bytesRead; while ((bytesRead = input.read(buffer)) != -1) { output.write(buffer, 0, bytesRead); } } 

Eso debería funcionar bien, básicamente, la llamada de read se bloqueará hasta que haya algunos datos disponibles, pero no esperará hasta que esté todo disponible para llenar el búfer. (Supongo que podría, y creo que FileInputStream normalmente llenará el búfer, pero una secuencia conectada a un socket es más probable que le proporcione los datos de inmediato).

Creo que al menos vale la pena probar esta solución simple.

¿Qué hay de solo usar

 void feedInputToOutput(InputStream in, OutputStream out) { IOUtils.copy(in, out); } 

y termine con eso?

de la biblioteca de E / S de jakarta apache commons que ya utilizan una gran cantidad de proyectos, por lo que probablemente ya tengas el jar en tu classpath.

Para completar, la guayaba también tiene una útil utilidad para esto

 ByteStreams.copy(input, output); 

Puedes usar un buffer circular:

Código

 // buffer all data in a circular buffer of infinite size CircularByteBuffer cbb = new CircularByteBuffer(CircularByteBuffer.INFINITE_SIZE); class1.putDataOnOutputStream(cbb.getOutputStream()); class2.processDataFromInputStream(cbb.getInputStream()); 

Dependencia Maven

  org.ostermiller utils 1.07.00  

Detalles del modo

http://ostermiller.org/utils/CircularBuffer.html

JDK 9 ha agregado InputStream#transferTo(OutputStream out) para esta funcionalidad.

Manera asíncrona de lograrlo.

 void inputStreamToOutputStream(final InputStream inputStream, final OutputStream out) { Thread t = new Thread(new Runnable() { public void run() { try { int d; while ((d = inputStream.read()) != -1) { out.write(d); } } catch (IOException ex) { //TODO make a callback on exception. } } }); t.setDaemon(true); t.start(); } 

BUFFER_SIZE es el tamaño de los mandriles para leer. Debe ser> 1kb y <10MB.

 private static final int BUFFER_SIZE = 2 * 1024 * 1024; private void copy(InputStream input, OutputStream output) throws IOException { try { byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead = input.read(buffer); while (bytesRead != -1) { output.write(buffer, 0, bytesRead); bytesRead = input.read(buffer); } //If needed, close streams. } finally { input.close(); output.close(); } } 

Esta es una versión de Scala que es limpia y rápida (sin stackoverflow):

  import scala.annotation.tailrec import java.io._ implicit class InputStreamOps(in: InputStream) { def >(out: OutputStream): Unit = pipeTo(out) def pipeTo(out: OutputStream, bufferSize: Int = 1<<10): Unit = pipeTo(out, Array.ofDim[Byte](bufferSize)) @tailrec final def pipeTo(out: OutputStream, buffer: Array[Byte]): Unit = in.read(buffer) match { case n if n > 0 => out.write(buffer, 0, n) pipeTo(out, buffer) case _ => in.close() out.close() } } 

Esto permite usar > símbolo, por ejemplo, inputstream > outputstream y también pasar en búferes / tamaños personalizados.

Use org.apache.commons.io.IOUtils

 InputStream inStream = new ... OutputStream outStream = new ... IOUtils.copy(inStream, outStream); 

o copyLarge para tamaño> 2 GB

En caso de que esté en funcionamiento, esta es una función escrita en Scala que muestra cómo puede copiar una secuencia de entrada a una secuencia de salida utilizando solo vals (y no vars).

 def copyInputToOutputFunctional(inputStream: InputStream, outputStream: OutputStream,bufferSize: Int) { val buffer = new Array[Byte](bufferSize); def recurse() { val len = inputStream.read(buffer); if (len > 0) { outputStream.write(buffer.take(len)); recurse(); } } recurse(); } 

Tenga en cuenta que no se recomienda su uso en una aplicación Java con poca memoria disponible porque con una función recursiva podría obtener fácilmente un error de excepción de desbordamiento de stack