Add connection retry logic

This commit is contained in:
Gaa56
2025-10-26 17:00:34 +00:00
parent bc1a8da160
commit 1524188b29

View File

@@ -1,21 +1,18 @@
package sd.protocol; package sd.protocol;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.net.ConnectException;
import java.net.Socket; import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.concurrent.TimeUnit;
/** /**
* Wrapper class that simplifies communication via Sockets. * Wrapper class that simplifies communication via Sockets.
* * It encapsulates all stream logic (ObjectInputStream/ObjectOutputStream) * Includes connection retry logic for robustness.
* so the rest of your code can simply "send" and "receive" objects
* that implement your MessageProtocol.
* * This is necessary to meet the requirement for inter-process communication
* (Intersections).
* * This class implements Closeable, so you can (and should) use it
* with a 'try-with-resources' block to ensure the socket is always closed.
*/ */
public class SocketConnection implements Closeable { public class SocketConnection implements Closeable {
@@ -23,46 +20,115 @@ public class SocketConnection implements Closeable {
private final ObjectOutputStream outputStream; private final ObjectOutputStream outputStream;
private final ObjectInputStream inputStream; private final ObjectInputStream inputStream;
// --- Configuration for Retry Logic ---
/** Maximum number of connection attempts. */
private static final int MAX_RETRIES = 5;
/** Delay between retry attempts in milliseconds. */
private static final long RETRY_DELAY_MS = 1000;
/** /**
* Constructor for the "Client" (who initiates the connection). * Constructor for the "Client" (who initiates the connection).
* Tries to connect to a process that is already listening (Server). * Tries to connect to a process that is already listening (Server).
* Includes retry logic in case of initial connection failure.
* *
* @param host The host address (e.g., "localhost" from your simulation.properties) * @param host The host address (e.g., "localhost" from your simulation.properties)
* @param port The port (e.g., 8001 from your simulation.properties) * @param port The port (e.g., 8001 from your simulation.properties)
* @throws IOException If connection fails. * @throws IOException If connection fails after all retries.
* @throws UnknownHostException If the host is not found. * @throws UnknownHostException If the host is not found (this error usually doesn't need retry).
* @throws InterruptedException If the thread is interrupted while waiting between retries.
*/ */
public SocketConnection(String host, int port) throws IOException, UnknownHostException { public SocketConnection(String host, int port) throws IOException, UnknownHostException, InterruptedException {
this.socket = new Socket(host, port); Socket tempSocket = null;
IOException lastException = null;
// IMPORTANT: The order is crucial to prevent deadlocks when creating streams.
// The OutputStream (output stream) must be created first. System.out.printf("[SocketConnection] Attempting to connect to %s:%d...%n", host, port);
this.outputStream = new ObjectOutputStream(socket.getOutputStream());
this.inputStream = new ObjectInputStream(socket.getInputStream()); // --- Retry Loop ---
for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
// Try to establish the connection
tempSocket = new Socket(host, port);
// If successful, break out of the retry loop
System.out.printf("[SocketConnection] Connected successfully on attempt %d.%n", attempt);
lastException = null; // Clear last error on success
break;
} catch (ConnectException | SocketTimeoutException e) {
// These are common errors indicating the server might not be ready.
lastException = e;
System.out.printf("[SocketConnection] Attempt %d/%d failed: %s. Retrying in %d ms...%n",
attempt, MAX_RETRIES, e.getMessage(), RETRY_DELAY_MS);
if (attempt < MAX_RETRIES) {
// Wait before the next attempt
TimeUnit.MILLISECONDS.sleep(RETRY_DELAY_MS);
}
} catch (IOException e) {
// Other IOExceptions might be more permanent, but we retry anyway.
lastException = e;
System.out.printf("[SocketConnection] Attempt %d/%d failed with IOException: %s. Retrying in %d ms...%n",
attempt, MAX_RETRIES, e.getMessage(), RETRY_DELAY_MS);
if (attempt < MAX_RETRIES) {
TimeUnit.MILLISECONDS.sleep(RETRY_DELAY_MS);
}
}
} // --- End of Retry Loop ---
// If after all retries tempSocket is still null, it means connection failed permanently.
if (tempSocket == null) {
System.err.printf("[SocketConnection] Failed to connect to %s:%d after %d attempts.%n", host, port, MAX_RETRIES);
if (lastException != null) {
throw lastException; // Throw the last exception encountered
} else {
// Should not happen if loop ran, but as a fallback
throw new IOException("Failed to connect after " + MAX_RETRIES + " attempts, reason unknown.");
}
}
// If connection was successful, assign to final variable and create streams
this.socket = tempSocket;
try {
// IMPORTANT: The order is crucial. OutputStream first.
this.outputStream = new ObjectOutputStream(socket.getOutputStream());
this.inputStream = new ObjectInputStream(socket.getInputStream());
} catch (IOException e) {
// If stream creation fails even after successful socket connection, clean up.
System.err.println("[SocketConnection] Failed to create streams after connection: " + e.getMessage());
try { socket.close(); } catch (IOException closeEx) { /* ignore */ }
throw e; // Re-throw the stream creation exception
}
} }
/** /**
* Constructor for the "Server" (who accepts the connection). * Constructor for the "Server" (who accepts the connection).
* Receives a Socket that has already been accepted by a ServerSocket. * Receives a Socket that has already been accepted by a ServerSocket.
* No retry logic needed here as the connection is already established.
* *
* @param acceptedSocket The Socket returned by serverSocket.accept(). * @param acceptedSocket The Socket returned by serverSocket.accept().
* @throws IOException If stream creation fails. * @throws IOException If stream creation fails.
*/ */
public SocketConnection(Socket acceptedSocket) throws IOException { public SocketConnection(Socket acceptedSocket) throws IOException {
this.socket = acceptedSocket; this.socket = acceptedSocket;
// IMPORTANT: The order is crucial. OutputStream (output stream) must be created first. // IMPORTANT: The order is crucial. OutputStream first.
this.outputStream = new ObjectOutputStream(socket.getOutputStream()); this.outputStream = new ObjectOutputStream(socket.getOutputStream());
this.inputStream = new ObjectInputStream(socket.getInputStream()); this.inputStream = new ObjectInputStream(socket.getInputStream());
System.out.printf("[SocketConnection] Connection accepted from %s:%d.%n",
acceptedSocket.getInetAddress().getHostAddress(), acceptedSocket.getPort());
} }
/** /**
* Sends (serializes) a MessageProtocol object over the socket. * Sends (serializes) a MessageProtocol object over the socket.
* *
* @param message The "envelope" (which contains the Vehicle) to be sent. * @param message The "envelope" (which contains the Vehicle) to be sent.
* @throws IOException If writing to the stream fails. * @throws IOException If writing to the stream fails or socket is not connected.
*/ */
public void sendMessage(MessageProtocol message) throws IOException { public void sendMessage(MessageProtocol message) throws IOException {
if (!isConnected()) {
throw new IOException("Socket is not connected.");
}
synchronized (outputStream) { synchronized (outputStream) {
outputStream.writeObject(message); outputStream.writeObject(message);
outputStream.flush(); // Ensures the message is sent immediately. outputStream.flush(); // Ensures the message is sent immediately.
@@ -74,10 +140,13 @@ public class SocketConnection implements Closeable {
* This call is "blocked" until an object is received. * This call is "blocked" until an object is received.
* *
* @return The "envelope" (MessageProtocol) that was received. * @return The "envelope" (MessageProtocol) that was received.
* @throws IOException If the connection is lost or the stream is corrupted. * @throws IOException If the connection is lost, the stream is corrupted, or socket is not connected.
* @throws ClassNotFoundException If the received object is unknown. * @throws ClassNotFoundException If the received object is unknown.
*/ */
public MessageProtocol receiveMessage() throws IOException, ClassNotFoundException { public MessageProtocol receiveMessage() throws IOException, ClassNotFoundException {
if (!isConnected()) {
throw new IOException("Socket is not connected.");
}
synchronized (inputStream) { synchronized (inputStream) {
return (MessageProtocol) inputStream.readObject(); return (MessageProtocol) inputStream.readObject();
} }
@@ -89,21 +158,24 @@ public class SocketConnection implements Closeable {
*/ */
@Override @Override
public void close() throws IOException { public void close() throws IOException {
System.out.printf("[SocketConnection] Closing connection to %s:%d.%n",
socket != null ? socket.getInetAddress().getHostAddress() : "N/A",
socket != null ? socket.getPort() : -1);
try { try {
if (inputStream != null) inputStream.close(); if (inputStream != null) inputStream.close();
} catch (IOException e) { /* ignore */ } } catch (IOException e) { /* ignore */ }
try { try {
if (outputStream != null) outputStream.close(); if (outputStream != null) outputStream.close();
} catch (IOException e) { /* ignore */ } } catch (IOException e) { /* ignore */ }
if (socket != null && !socket.isClosed()) { if (socket != null && !socket.isClosed()) {
socket.close(); socket.close();
} }
} }
/** /**
* @return true if the socket is still connected. * @return true if the socket is still connected and not closed.
*/ */
public boolean isConnected() { public boolean isConnected() {
return socket != null && socket.isConnected() && !socket.isClosed(); return socket != null && socket.isConnected() && !socket.isClosed();