¿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:
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.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:
Respuesta actualizada. –
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. –