¿Por qué las escrituras de SocketChannel siempre se completan por la cantidad total, incluso en sockets sin bloqueo?

StackOverflow https://stackoverflow.com/questions/158121

Pregunta

Utilizando Sun Java VM 1.5 o 1.6 en Windows, conecto un zócalo sin bloqueo. Luego lleno un ByteBuffer con un mensaje para enviar e intento write () en el SocketChannel.

Espero que la escritura se complete solo parcialmente si la cantidad a escribir es mayor que la cantidad de espacio en el búfer de salida TCP del socket (esto es lo que espero intuitivamente, también es más o menos mi comprensión de documentos ), pero eso no es lo que sucede. El write () siempre parece regresar informando la cantidad total escrita, incluso si son varios megabytes (el SO_SNDBUF del socket es 8KB, mucho, mucho menos que mi salida de varios megabytes mensaje).

Un problema aquí es que no puedo probar el código que maneja el caso donde la salida está parcialmente escrita (registrando un conjunto de interés de WRITE en un selector y haciendo una selección de ( ) para esperar hasta que se pueda escribir el resto), ya que ese caso nunca parece suceder. ¿Qué no estoy entendiendo?

¿Fue útil?

Solución

Logré reproducir una situación que podría ser similar a la tuya. Creo que, irónicamente, su destinatario está consumiendo los datos más rápido de lo que lo está escribiendo.

import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class MyServer {
  public static void main(String[] args) throws Exception {
    final ServerSocket ss = new ServerSocket(12345);
    final Socket cs = ss.accept();
    System.out.println("Accepted connection");

    final InputStream in = cs.getInputStream();
    final byte[] tmp = new byte[64 * 1024];
    while (in.read(tmp) != -1);

    Thread.sleep(100000);
  }
}



import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class MyNioClient {
  public static void main(String[] args) throws Exception {
    final SocketChannel s = SocketChannel.open();
    s.configureBlocking(false);
    s.connect(new InetSocketAddress("localhost", 12345));
    s.finishConnect();

    final ByteBuffer buf = ByteBuffer.allocate(128 * 1024);
    for (int i = 0; i < 10; i++) {
      System.out.println("to write: " + buf.remaining() + ", written: " + s.write(buf));
      buf.position(0);
    }
    Thread.sleep(100000);
  }
}

Si ejecuta el servidor anterior y luego hace que el cliente anterior intente escribir 10 fragmentos de 128 kB de datos, verá que cada operación de escritura escribe todo el búfer sin bloqueo. Sin embargo, si modifica el servidor anterior para no leer nada de la conexión, verá que solo la primera operación de escritura en el cliente escribirá 128 kB, mientras que todas las escrituras posteriores devolverán 0 .

Salida cuando el servidor está leyendo desde la conexión:

to write: 131072, written:  131072
to write: 131072, written:  131072
to write: 131072, written:  131072
...

Salida cuando el servidor no lee desde la conexión:

to write: 131072, written:  131072
to write: 131072, written:  0
to write: 131072, written:  0
...  

Otros consejos

He estado trabajando con UDP en Java y he visto algunos realmente interesantes. y comportamiento completamente indocumentado en el material Java NIO en general. La mejor manera de determinar lo que está sucediendo es mirar la fuente que viene con Java.

También apostaría bastante a que podría encontrar una mejor implementación de lo que está buscando en cualquier otra implementación de JVM, como la de IBM, pero no puedo garantizarlo sin mirarlas yo mismo.

No puedo encontrarlo documentado en ningún lado, pero IIRC [1], send () tiene la garantía de a) enviar el búfer suministrado por completo, o b) falla. Nunca completará el envío parcialmente.

[1] He escrito múltiples implementaciones de Winsock (para Win 3.0, Win 95, Win NT, etc.), por lo que este puede ser un comportamiento específico de Winsock (en lugar de sockets genéricos).

Daré un gran salto de fe y asumiré que el proveedor de red subyacente para Java es el mismo que para C ... el O / S asigna más que solo SO_SNDBUF para cada socket. Apuesto a que si coloca su código de envío en un bucle for (1,100000), eventualmente obtendrá una escritura que tendrá éxito con un valor menor al solicitado.

Realmente debería mirar un marco NIO como MINA o Grizzly . He usado MINA con gran éxito en un servidor de chat empresarial. También se utiliza en el servidor de chat Openfire . Grizzly se utiliza en la implementación JavaEE de Sun.

¿Dónde envías los datos? Tenga en cuenta que la red actúa como un búfer que tiene al menos el mismo tamaño que su SO_SNDBUF más el SO_RCVBUF del receptor. Agregue esto a la actividad de lectura del receptor como lo menciona Alexander y puede obtener una gran cantidad de datos absorbidos.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top