2010-01-08 10 views
13

¿Cuál es la forma más adecuada de detectar si un socket se ha caído o no? O si un paquete realmente fue enviado?Java Sockets and Dropped Connections

Tengo una biblioteca para enviar notificaciones de Apple Push a iPhones a través de Apple Gatways (available on GitHub). Los clientes necesitan abrir un socket y enviar una representación binaria de cada mensaje; pero lamentablemente Apple no devuelve ningún reconocimiento en absoluto. La conexión se puede reutilizar para enviar mensajes múltiples también. Estoy usando las conexiones simples de Java Socket. El código relevante es:

Socket socket = socket(); // returns an reused open socket, or a new one 
socket.getOutputStream().write(m.marshall()); 
socket.getOutputStream().flush(); 
logger.debug("Message \"{}\" sent", m); 

En algunos casos, si se corta una conexión mientras se envía un mensaje o justo antes; Socket.getOutputStream().write() termina con éxito. Espero que se deba a que la ventana de TCP todavía no está agotada.

¿Hay alguna manera de saber con certeza si un paquete realmente se conectó a la red o no? Experimenté con las dos soluciones siguientes:

  1. insertar una operación adicional socket.getInputStream().read() con un tiempo de espera de 250 ms. Esto obliga a una operación de lectura que falla cuando se desconectó la conexión, pero se bloquea de otra manera por 250 ms.

  2. configure el tamaño del búfer de envío TCP (por ejemplo, Socket.setSendBufferSize()) al tamaño binario del mensaje.

Ambos métodos funcionan, pero degradan significativamente la calidad del servicio; el rendimiento va de 100 mensajes/segundo a aproximadamente 10 mensajes/segundo como máximo.

¿Alguna sugerencia?

ACTUALIZACIÓN:

Ante el desafío de múltiples respuestas que cuestionan la posibilidad de que el descrito. Construí pruebas de "unidad" del comportamiento que estoy describiendo. Consulte las cajas de la unidad al Gist 273786.

Ambas pruebas de unidad tienen dos hilos, un servidor y un cliente. El servidor se cierra mientras el cliente está enviando datos sin una IOException lanzada de todos modos. Este es el método principal:

public static void main(String[] args) throws Throwable { 
    final int PORT = 8005; 
    final int FIRST_BUF_SIZE = 5; 

    final Throwable[] errors = new Throwable[1]; 
    final Semaphore serverClosing = new Semaphore(0); 
    final Semaphore messageFlushed = new Semaphore(0); 

    class ServerThread extends Thread { 
     public void run() { 
      try { 
       ServerSocket ssocket = new ServerSocket(PORT); 
       Socket socket = ssocket.accept(); 
       InputStream s = socket.getInputStream(); 
       s.read(new byte[FIRST_BUF_SIZE]); 

       messageFlushed.acquire(); 

       socket.close(); 
       ssocket.close(); 
       System.out.println("Closed socket"); 

       serverClosing.release(); 
      } catch (Throwable e) { 
       errors[0] = e; 
      } 
     } 
    } 

    class ClientThread extends Thread { 
     public void run() { 
      try { 
       Socket socket = new Socket("localhost", PORT); 
       OutputStream st = socket.getOutputStream(); 
       st.write(new byte[FIRST_BUF_SIZE]); 
       st.flush(); 

       messageFlushed.release(); 
       serverClosing.acquire(1); 

       System.out.println("writing new packets"); 

       // sending more packets while server already 
       // closed connection 
       st.write(32); 
       st.flush(); 
       st.close(); 

       System.out.println("Sent"); 
      } catch (Throwable e) { 
       errors[0] = e; 
      } 
     } 
    } 

    Thread thread1 = new ServerThread(); 
    Thread thread2 = new ClientThread(); 

    thread1.start(); 
    thread2.start(); 

    thread1.join(); 
    thread2.join(); 

    if (errors[0] != null) 
     throw errors[0]; 
    System.out.println("Run without any errors"); 
} 

[Por cierto, también tienen una biblioteca de pruebas de concurrencia, que hace que la configuración un poco mejor y más clara. Verifique la muestra en la esencia también].

Cuando se ejecuta me sale el siguiente resultado:

+0

Respuesta actualizada. –

+0

Como ya indiqué en mi respuesta, si quiere asegurarse de que no hubo ningún error, debe realizar una terminación de conexión elegante. Ejecute shutdownOutput(), luego lea hasta que reciba EOF, luego cierre el socket. La terminación de conexión elegante es, en esencia, recibir un reconocimiento por parte del par que lo recibió bien, que es exactamente lo que quiere. –

Respuesta

17

Esto no será de mucha ayuda para usted, pero técnicamente ambos de sus soluciones propuestas son incorrectas. OutputStream.flush() y cualquier otra llamada API que se te ocurra, no harán lo que necesites.

La única manera portátil y confiable de determinar si un paquete ha sido recibido por el par es esperar la confirmación del par. Esta confirmación puede ser una respuesta real o un cierre de socket elegante. Fin de la historia: realmente no hay otra manera, y esto no es específico de Java, es una programación de red fundamental.

Si esto no es una conexión persistente, es decir, si solo envía algo y luego cierra la conexión, la forma en que lo hace es atrapar todas las IOExcepciones (cualquiera de ellas indica un error) y realiza un elegante zócalo apagado:

1. socket.shutdownOutput(); 
2. wait for inputStream.read() to return -1, indicating the peer has also shutdown its socket 
+1

Completamente correcto. Si la capa de aplicación no le da ninguna confirmación, entonces el protocolo es simplemente poco confiable, y tendrá que aguantar eso (el simple hecho de saber que el paquete salió en el cable no le dice que en realidad Llegué al otro extremo en una sola pieza, después de todo). – caf

2

Si va a enviar información mediante el protocolo TCP/IP para la manzana tienes que estar recibiendo reconocimientos. Sin embargo usted declaró:

Apple no devuelve ningún reconocimiento alguno

¿Qué quiere decir con esto? TCP/IP garantiza la entrega, por lo que el receptor DEBE acusar recibo. Sin embargo, no garantiza cuándo se realizará la entrega.

Si envía una notificación a Apple y rompe su conexión antes de recibir el ACK, no hay forma de saber si tuvo éxito o no, así que simplemente debe enviarlo nuevamente. Si presionar la misma información dos veces es un problema o el dispositivo no lo maneja correctamente, entonces hay un problema. La solución es arreglar el manejo del dispositivo de la notificación de inserción duplicada: no hay nada que puedas hacer en el lado de empuje.

@Comment Aclaración/Pregunta

Ok. La primera parte de lo que entiendes es tu respuesta a la segunda parte. Solo los paquetes que han recibido ACKS han sido enviados y recibidos correctamente. Estoy seguro de que podríamos pensar en algún esquema muy complicado de hacer un seguimiento de cada paquete individual, pero se supone que TCP debe abstraer esta capa y manejarla por usted. Por su parte, simplemente tiene que lidiar con la multitud de fallas que podrían ocurrir (en Java si ocurre alguna de estas excepciones se genera una excepción). Si no hay excepciones, los datos que acaba de tratar de enviar se envían garantizados por el protocolo TCP/IP.

¿Hay una situación en la que los datos aparentemente se "envían" pero no se garantiza que se reciban cuando no se produce ninguna excepción? La respuesta debería ser no.

@Examples

Niza ejemplos, esto aclara las cosas un poco. Pensé que se lanzaría un error. En el ejemplo publicado, se genera un error en la segunda escritura, pero no en la primera. Este es un comportamiento interesante ... y no pude encontrar mucha información explicando por qué se comporta así. Sin embargo, explica por qué debemos desarrollar nuestros propios protocolos de nivel de aplicación para verificar la entrega.

Parece que tiene razón en que, sin un protocolo de confirmación, no hay garantía de que el dispositivo Apple reciba la notificación. Apple también solo pone en cola el último mensaje. Al mirar un poco el servicio, pude determinar que este servicio es más conveniente para el cliente, pero no se puede usar para garantizar el servicio y se debe combinar con otros métodos. Lo leí de la siguiente fuente.

http://blog.boxedice.com/2009/07/10/how-to-build-an-apple-push-notification-provider-server-tutorial/

parece que la respuesta es no, de si o no se puede decir con seguridad. Es posible que pueda utilizar un detector de paquetes como Wireshark para saber si se envió, pero esto aún no garantiza que se haya recibido y enviado al dispositivo debido a la naturaleza del servicio.

+0

Quiero decir que Apple no devuelve ningún reconocimiento de protocolo específico más allá de TCP ACK. Tampoco sé cómo puedo decir qué paquetes se enviaron correctamente y cuáles no. – notnoop

+0

Gracias por la actualización. Me doy cuenta de que debería haber centrado la pregunta en TCP con los ejemplos, en lugar de discutir los servicios de Apple. Creo que me rendiré por el soporte de detección de conexión caído por ahora. – notnoop

3

Después de muchos problemas con conexiones caídas, moví mi código para use the enhanced format, que más o menos significa que cambia su paquete a tener este aspecto:

enter image description here

De esta manera Apple no va a caer una conexión si una Se produce un error, pero se escribirá un código de retroalimentación en el socket.