Pregunta

¿Cuál es la forma más adecuada de detectar si un enchufe se ha caído o no?¿O si realmente se envió un paquete?

Tengo una biblioteca para enviar notificaciones push de Apple a iPhones a través de las pasarelas de Apple (disponible en GitHub).Los clientes necesitan abrir un socket y enviar una representación binaria de cada mensaje;pero desafortunadamente Apple no devuelve ningún reconocimiento.La conexión también se puede reutilizar para enviar varios mensajes.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 interrumpe una conexión mientras se envía un mensaje o justo antes; Socket.getOutputStream().write() Sin embargo, termina con éxito.Supongo que se debe a que la ventana TCP aún no está agotada.

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

  1. Insertar un adicional socket.getInputStream().read() operación con un tiempo de espera de 250 ms.Esto fuerza una operación de lectura que falla cuando se interrumpe la conexión, pero que en caso contrario se bloquea durante 250 ms.

  2. establezca el tamaño del búfer de envío TCP (p. ej. 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?

ACTUALIZAR:

Cuestionado por múltiples respuestas que cuestionan la posibilidad de lo descrito.Construí pruebas "unitarias" del comportamiento que estoy describiendo.Consulte las cajas unitarias en Esencia 273786.

Ambas pruebas unitarias tienen dos subprocesos, un servidor y un cliente.El servidor se cierra mientras el cliente envía datos sin que se produzca una IOException de todos modos.Aquí está 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 tengo una biblioteca de pruebas de concurrencia, que hace que la configuración sea un poco mejor y más clara.Consulte también la muestra en esencia].

Cuando lo ejecuto obtengo el siguiente resultado:

Closed socket
writing new packets
Finished writing
Run without any errors
¿Fue útil?

Solución

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

La única forma portátil y confiable de determinar si el par ha recibido un paquete es esperar una confirmación del par.Esta confirmación puede ser una respuesta real o un cierre elegante del socket.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 esta no es una conexión persistente, es decir, si simplemente envía algo y luego cierra la conexión, la forma de hacerlo es capturar todas las IOExceptions (cualquiera de ellas indica un error) y realizar un cierre elegante del socket:

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

Otros consejos

Después de muchos problemas con conexiones caídas, que se ha llevado mi código para utilizar el formato mejorado, que más o menos significa que cambia su paquete a tener este aspecto:

introducir descripción de la imagen aquí

De esta manera Apple no va a caer una conexión si ocurre un error, pero va a escribir un código de información a la toma de corriente.

Si envía información utilizando el protocolo TCP/IP a Apple, debe recibir acuses de recibo.Sin embargo usted dijo:

Apple no devuelve ningún reconocimiento

¿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 e interrumpe su conexión antes de recibir el ACK, no hay forma de saber si tuvo éxito o no, por lo que simplemente debe enviarla nuevamente.Si enviar la misma información dos veces es un problema o el dispositivo no la maneja adecuadamente, entonces hay un problema.La solución es arreglar el manejo del dispositivo de la notificación push duplicada:no hay nada que puedas hacer en el lado de empujar.

@Comentario Aclaración/Pregunta

De acuerdo.La primera parte de lo que entiendes es tu respuesta a la segunda parte.Solo los paquetes que recibieron ACKS se enviaron y recibieron correctamente.Estoy seguro de que podríamos pensar en algún esquema muy complicado para realizar un seguimiento de cada paquete individual nosotros mismos, pero se supone que TCP abstrae esta capa y la maneja 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, se genera una excepción).Si no hay ninguna excepción, los datos que acaba de intentar enviar se envían garantizados por el protocolo TCP/IP.

¿Existe una situación en la que aparentemente se "envían" datos pero no se garantiza su recepción y no se plantea ninguna excepción?La respuesta debería ser no.

@Ejemplos

Buenos ejemplos, esto aclara bastante las cosas.Pensé que se produciría un error.En el ejemplo publicado, se produce un error en la segunda escritura, pero no en la primera.Este es un comportamiento interesante...y no pude encontrar mucha información que explique por qué se comporta así.Sin embargo, explica por qué debemos desarrollar nuestros propios protocolos a 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 observar un poco el servicio, pude determinar que este servicio es más para la conveniencia del cliente, pero no puede usarse para garantizar el servicio y debe combinarse con otros métodos.Leí esto 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 sobre si se puede saber con certeza o no.Es posible que puedas utilizar un rastreador 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.

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