mirror of
https://github.com/davidalves04/Trabalho-Pratico-SD.git
synced 2025-12-08 12:33:31 +00:00
Merge pull request #22 from davidalves04/10-create-network-communication-classes
Create network communication classes
This commit is contained in:
41
main/src/main/java/sd/protocol/MessageProtocol.java
Normal file
41
main/src/main/java/sd/protocol/MessageProtocol.java
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package sd.protocol;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import sd.model.MessageType; // Assuming MessageType is in sd.model or sd.protocol
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface defining the contract for all messages exchanged in the simulator.
|
||||||
|
* Ensures that any message can be identified and routed.
|
||||||
|
* * This interface extends Serializable to allow objects that implement it
|
||||||
|
* to be sent over Sockets (ObjectOutputStream).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface MessageProtocol extends Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of the message, indicating its purpose.
|
||||||
|
* @return The MessageType (e.g., VEHICLE_TRANSFER, STATS_UPDATE).
|
||||||
|
*/
|
||||||
|
MessageType getType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data object (payload) that this message carries.
|
||||||
|
* The type of object will depend on the MessageType.
|
||||||
|
* * - If getType() == VEHICLE_TRANSFER, the payload will be a {@link sd.model.Vehicle} object.
|
||||||
|
* - If getType() == STATS_UPDATE, the payload will be a statistics object.
|
||||||
|
* * @return The data object (payload), which must also be Serializable.
|
||||||
|
*/
|
||||||
|
Object getPayload();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of the node (Process) that sent this message.
|
||||||
|
* @return String (e.g., "Cr1", "Cr5", "S").
|
||||||
|
*/
|
||||||
|
String getSourceNode();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of the destination node (Process) for this message.
|
||||||
|
* @return String (e.g., "Cr2", "DashboardServer").
|
||||||
|
*/
|
||||||
|
String getDestinationNode();
|
||||||
|
}
|
||||||
183
main/src/main/java/sd/protocol/SocketConnection.java
Normal file
183
main/src/main/java/sd/protocol/SocketConnection.java
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
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.
|
||||||
|
* Includes connection retry logic for robustness.
|
||||||
|
*/
|
||||||
|
public class SocketConnection implements Closeable {
|
||||||
|
|
||||||
|
private final Socket socket;
|
||||||
|
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 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, 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 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 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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to read (deserialize) a MessageProtocol object from the socket.
|
||||||
|
* This call is "blocked" until an object is received.
|
||||||
|
*
|
||||||
|
* @return The "envelope" (MessageProtocol) that was received.
|
||||||
|
* @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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the socket and all streams (Input and Output).
|
||||||
|
* It is called automatically if you use 'try-with-resources'.
|
||||||
|
*/
|
||||||
|
@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 and not closed.
|
||||||
|
*/
|
||||||
|
public boolean isConnected() {
|
||||||
|
return socket != null && socket.isConnected() && !socket.isClosed();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user