tutorialspoint thread multithread java networking network-programming nio

multithread - thread java tutorialspoint



NIO de Java: el envío de mensajes de gran tamaño genera rápidamente paquetes truncados y pérdida de datos (5)

Tengo este desagradable problema al enviar múltiples mensajes grandes en una sucesión rápida desde un servidor Java (NIO) (que ejecuta Linux) a un cliente dará lugar a paquetes truncados. Los mensajes deben ser grandes y enviados muy rápidamente para que ocurra el problema. Esto es básicamente lo que hace mi código (no el código real, sino más o menos lo que está sucediendo):

//-- setup stuff: -- Charset charset = Charset.forName("UTF-8"); CharsetEncoder encoder = charset.newEncoder(); String msg = "A very long message (let''s say 20KB)..."; //-- inside loop to handle incoming connections: -- ServerSocketChannel ssc = (ServerSocketChannel)key.channel(); SocketChannel sc = ssc.accept(); sc.configureBlocking(false); sc.socket().setTcpNoDelay(true); sc.socket().setSendBufferSize(1024*1024); //-- later, actual sending of messages: -- for (int n=0; n<20; n++){ ByteBuffer bb = encoder.encode(CharBuffer.wrap(msg+''/0'')); sc.write(bb); bb.rewind(); }

Por lo tanto, si los paquetes son lo suficientemente largos y se envían lo más rápido posible (es decir, en un ciclo como este sin demora), en el otro extremo a menudo sale algo como esto:

[COMPLETE PACKET 1] [COMPLETE PACKET 2] [COMPLETE PACKET 3] [START OF PACKET 4][SOME OR ALL OF PACKET 5]

Hay pérdida de datos, y los paquetes comienzan a ejecutarse juntos, de modo que el inicio del paquete 5 (en este ejemplo) llega en el mismo mensaje que el inicio del paquete 4. No es solo truncar, es ejecutar los mensajes juntos.

Imagino que esto está relacionado con el búfer TCP o el "tamaño de ventana", o que el servidor aquí solo proporciona datos más rápido que el sistema operativo, o el adaptador de red, o algo así, puede manejarlo. ¿Pero cómo verifico y evito que suceda? Si reduzco la longitud del mensaje por uso de sc.write (), pero luego aumento las repeticiones, aún así me encontraré con el mismo problema. Parece ser simplemente un problema con la cantidad de datos en un corto período de tiempo. No veo que sc.write () arroje ninguna excepción tampoco (sé que en mi ejemplo anterior no estoy revisando, pero tengo en mis pruebas).

Me alegraría si pudiera verificar programáticamente si todavía no está listo para más datos, poner un retraso y esperar hasta que esté listo. Tampoco estoy seguro si "sc.socket (). SetSendBufferSize (1024 * 1024);" tiene algún efecto, o si tendría que ajustar esto en el lado de Linux de las cosas. ¿Hay alguna manera de "limpiar" realmente un SocketChannel? Como una solución coja, podría tratar de forzar explícitamente un envío completo de todo lo que se almacena en el búfer cada vez que estoy tratando de enviar un mensaje de más de 10 KB, por ejemplo (que no es tan frecuente en mi aplicación). Pero no sé de ninguna manera forzar un envío del búfer (o esperar hasta que haya enviado). ¡Gracias por cualquier ayuda!


Hay muchas razones por las cuales sc.write () no enviaría algunos o todos los datos. Debe verificar el valor de retorno y / o el número de bytes restantes en el búfer.

for (int n=0; n<20; n++){ ByteBuffer bb = encoder.encode(CharBuffer.wrap(msg+''/0'')); if(sc.write(bb) > 0 && bb.remaining() == 0) { // all data sent } else { // could not send all data. } bb.rewind(); }


No está verificando el valor de retorno de:

sc.write(bb);

Esto devuelve la cantidad de bytes escritos, que pueden ser menores que los datos disponibles en su búfer. Debido a cómo funciona nio, probablemente puedas simplemente llamar a remaining () en tu bytebuffer para ver si queda alguno.


No he hecho ninguna programación NIO, pero según Javadocs, sc.write () no escribirá el ByteBuffer completo si el SocketChannel está en modo no bloqueante (como el tuyo) y el búfer de salida del zócalo está lleno.

Debido a que está escribiendo tan rápido, es muy probable que esté inundando su conexión y que su red o receptor no pueda mantener el ritmo.

Estaría feliz si pudiera verificar programáticamente si todavía no está listo para más datos

Debe comprobar el valor de retorno de sc.write () para averiguar si su búfer de salida está lleno.


Está utilizando el modo no bloqueante: sc.configureBlocking ( false );

Establezca el bloqueo en verdadero y su código debería funcionar como está. Las sugerencias hechas por otros aquí para verificar el conteo de envíos y el bucle también funcionarán.