From 1524188b29ec2539d9b24a59c203be3222d87d2f Mon Sep 17 00:00:00 2001 From: Gaa56 Date: Sun, 26 Oct 2025 17:00:34 +0000 Subject: [PATCH] Add connection retry logic --- .../java/sd/protocol/SocketConnection.java | 120 ++++++++++++++---- 1 file changed, 96 insertions(+), 24 deletions(-) diff --git a/main/src/main/java/sd/protocol/SocketConnection.java b/main/src/main/java/sd/protocol/SocketConnection.java index 0198242..f6392a4 100644 --- a/main/src/main/java/sd/protocol/SocketConnection.java +++ b/main/src/main/java/sd/protocol/SocketConnection.java @@ -1,21 +1,18 @@ -package sd.protocol; +package sd.protocol; import java.io.Closeable; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.net.ConnectException; import java.net.Socket; +import java.net.SocketTimeoutException; import java.net.UnknownHostException; +import java.util.concurrent.TimeUnit; /** * Wrapper class that simplifies communication via Sockets. - * * It encapsulates all stream logic (ObjectInputStream/ObjectOutputStream) - * 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. + * Includes connection retry logic for robustness. */ public class SocketConnection implements Closeable { @@ -23,46 +20,115 @@ public class SocketConnection implements Closeable { private final ObjectOutputStream outputStream; 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). * 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 port The port (e.g., 8001 from your simulation.properties) - * @throws IOException If connection fails. - * @throws UnknownHostException If the host is not found. + * @throws IOException If connection fails after all retries. + * @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 { - this.socket = new Socket(host, port); - - // IMPORTANT: The order is crucial to prevent deadlocks when creating streams. - // The OutputStream (output stream) must be created first. - this.outputStream = new ObjectOutputStream(socket.getOutputStream()); - this.inputStream = new ObjectInputStream(socket.getInputStream()); + public SocketConnection(String host, int port) throws IOException, UnknownHostException, InterruptedException { + Socket tempSocket = null; + IOException lastException = null; + + System.out.printf("[SocketConnection] Attempting to connect to %s:%d...%n", host, port); + + // --- 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). * 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(). * @throws IOException If stream creation fails. */ public SocketConnection(Socket acceptedSocket) throws IOException { 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.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. * * @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 { + if (!isConnected()) { + throw new IOException("Socket is not connected."); + } synchronized (outputStream) { outputStream.writeObject(message); 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. * * @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. */ public MessageProtocol receiveMessage() throws IOException, ClassNotFoundException { + if (!isConnected()) { + throw new IOException("Socket is not connected."); + } synchronized (inputStream) { return (MessageProtocol) inputStream.readObject(); } @@ -89,21 +158,24 @@ public class SocketConnection implements Closeable { */ @Override 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 { if (inputStream != null) inputStream.close(); } catch (IOException e) { /* ignore */ } - + try { if (outputStream != null) outputStream.close(); } catch (IOException e) { /* ignore */ } - + if (socket != null && !socket.isClosed()) { socket.close(); } } /** - * @return true if the socket is still connected. + * @return true if the socket is still connected and not closed. */ public boolean isConnected() { return socket != null && socket.isConnected() && !socket.isClosed();