a = new ArrayList<>(this.networkClients.values());
// request.future().complete(a);
+ // TODO
}
public void handleShutdownAll(NetworkEvents.ForceCloseAllClients request) {
diff --git a/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java b/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java
index cdd3a82..64d8082 100644
--- a/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java
+++ b/framework/src/main/java/org/toop/framework/networking/NetworkingClientManager.java
@@ -1,10 +1,20 @@
package org.toop.framework.networking;
+import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.toop.framework.eventbus.EventFlow;
+import org.toop.framework.networking.events.NetworkEvents;
+import org.toop.framework.networking.exceptions.ClientNotFoundException;
+import org.toop.framework.networking.exceptions.CouldNotConnectException;
import org.toop.framework.networking.interfaces.NetworkingClient;
+import org.toop.framework.networking.types.NetworkingReconnect;
public class NetworkingClientManager implements org.toop.framework.networking.interfaces.NetworkingClientManager {
private static final Logger logger = LogManager.getLogger(NetworkingClientManager.class);
@@ -13,46 +23,169 @@ public class NetworkingClientManager implements org.toop.framework.networking.in
public NetworkingClientManager() {}
@Override
- public OptionalLong startClient(long id, NetworkingClient networkingClient, String host, int port) {
- try {
- networkingClient.connect(id, host, port);
- this.networkClients.put(id, networkingClient);
- logger.info("New client started successfully for {}:{}", host, port);
- } catch (Exception e) {
- logger.error(e); // TODO Better error handling
- return OptionalLong.empty();
- }
- return OptionalLong.of(id);
+ public void startClient(
+ long id,
+ NetworkingClient networkingClient,
+ String host, int port,
+ NetworkingReconnect networkingReconnect) {
+
+ ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+
+ Runnable connectTask = new Runnable() {
+ int attempts = 0;
+
+ @Override
+ public void run() {
+ try {
+ networkingClient.connect(id, host, port);
+ networkClients.put(id, networkingClient);
+ logger.info("New client started successfully for {}:{}", host, port);
+ new EventFlow().addPostEvent(new NetworkEvents.StartClientResponse(id, id)).postEvent();
+ scheduler.shutdown();
+ } catch (CouldNotConnectException e) {
+ attempts++;
+ if (attempts < networkingReconnect.reconnectAttempts()) {
+ logger.warn("Could not connect to {}:{}. Retrying in {} {}",
+ host, port, networkingReconnect.timeout(), networkingReconnect.timeUnit());
+ scheduler.schedule(this, networkingReconnect.timeout(), networkingReconnect.timeUnit());
+ } else {
+ logger.error("Failed to start client for {}:{} after {} attempts", host, port, attempts);
+ new EventFlow().addPostEvent(new NetworkEvents.StartClientResponse(-1, id)).postEvent();
+ scheduler.shutdown();
+ }
+ } catch (Exception e) {
+ logger.error("Unexpected exception during startClient", e);
+ scheduler.shutdown();
+ }
+ }
+ };
+
+ scheduler.schedule(connectTask, 0, TimeUnit.MILLISECONDS);
}
@Override
- public boolean sendCommand(long id, String command) {
+ public void sendCommand(long id, String command) throws ClientNotFoundException {
logger.info("Sending command to client for {}:{}", id, command);
- if (command.isEmpty()) { return false; }
+ if (command.isEmpty()) {
+ IllegalArgumentException e = new IllegalArgumentException("command is empty");
+ logger.error("Invalid command received", e);
+ return;
+ }
NetworkingClient client = this.networkClients.get(id);
- if (client == null) { return false; } // TODO: Create client not found exceptions.
+ if (client == null) {
+ throw new ClientNotFoundException(id);
+ }
String toSend = command.trim();
if (toSend.endsWith("\n")) { client.writeAndFlush(toSend); }
else { client.writeAndFlush(toSend + "\n"); }
- return true;
}
@Override
- public boolean reconnect(long id) {
- return false; // TODO
+ public void reconnect(long id, NetworkingReconnect networkingReconnect) throws ClientNotFoundException {
+ NetworkingClient client = this.networkClients.get(id);
+ if (client == null) {
+ throw new ClientNotFoundException(id);
+ }
+
+ InetSocketAddress address = client.getAddress();
+
+ if (client.isActive()) {
+ client.closeConnection();
+ }
+
+ ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+
+ Runnable connectTask = new Runnable() {
+ int attempts = 0;
+
+ @Override
+ public void run() {
+ try {
+ client.connect(id, address.getHostName(), address.getPort());
+ networkClients.put(id, client);
+ logger.info("Client {} reconnected to {}:{}", id, address.getHostName(), address.getPort());
+ new EventFlow().addPostEvent(new NetworkEvents.ReconnectResponse(true, id)).postEvent().postEvent();
+ scheduler.shutdown();
+ } catch (CouldNotConnectException e) {
+ attempts++;
+ if (attempts < networkingReconnect.reconnectAttempts()) {
+ logger.warn("Could not reconnect client {} to {}:{}. Retrying in {} {}",
+ id, address.getHostName(), address.getPort(), networkingReconnect.timeout(), networkingReconnect.timeUnit());
+ scheduler.schedule(this, networkingReconnect.timeout(), networkingReconnect.timeUnit());
+ } else {
+ logger.error("Failed to reconnect client {} to {}:{} after {} attempts", id, address.getHostName(), address.getPort(), attempts);
+ new EventFlow().addPostEvent(new NetworkEvents.ReconnectResponse(false, id)).postEvent().postEvent();
+ scheduler.shutdown();
+ }
+ } catch (Exception e) {
+ logger.error("Unexpected exception during reconnect for client {}", id, e);
+ new EventFlow().addPostEvent(new NetworkEvents.ReconnectResponse(false, id)).postEvent().postEvent();
+ scheduler.shutdown();
+ }
+ }
+ };
+
+ scheduler.schedule(connectTask, 0, TimeUnit.MILLISECONDS);
}
@Override
- public boolean changeAddress(long id, String host, int port) {
- return false; // TODO
+ public void changeAddress(long id, String host, int port, NetworkingReconnect networkingReconnect) throws ClientNotFoundException {
+ NetworkingClient client = this.networkClients.get(id);
+ if (client == null) {
+ throw new ClientNotFoundException(id);
+ }
+
+ if (client.isActive()) {
+ client.closeConnection();
+ }
+
+ ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+
+ Runnable connectTask = new Runnable() {
+ int attempts = 0;
+
+ @Override
+ public void run() {
+ try {
+ client.connect(id, host, port);
+ networkClients.put(id, client);
+ logger.info("Client {} changed address to {}:{}", id, host, port);
+ new EventFlow().addPostEvent(new NetworkEvents.ChangeAddressResponse(true, id)).postEvent().postEvent();
+ scheduler.shutdown();
+ } catch (CouldNotConnectException e) {
+ attempts++;
+ if (attempts < networkingReconnect.reconnectAttempts()) {
+ logger.warn("Could not connect client {} to {}:{}. Retrying in {} {}",
+ id, host, port, networkingReconnect.timeout(), networkingReconnect.timeUnit());
+ scheduler.schedule(this, networkingReconnect.timeout(), networkingReconnect.timeUnit());
+ } else {
+ logger.error("Failed to connect client {} to {}:{} after {} attempts", id, host, port, attempts);
+ new EventFlow().addPostEvent(new NetworkEvents.ChangeAddressResponse(false, id)).postEvent().postEvent();
+ scheduler.shutdown();
+ }
+ } catch (Exception e) {
+ logger.error("Unexpected exception during changeAddress for client {}", id, e);
+ new EventFlow().addPostEvent(new NetworkEvents.ChangeAddressResponse(false, id)).postEvent().postEvent();
+ scheduler.shutdown();
+ }
+ }
+ };
+
+ scheduler.schedule(connectTask, 0, TimeUnit.MILLISECONDS);
}
@Override
- public boolean closeClient(long id) {
- return false; // TODO
+ public void closeClient(long id) throws ClientNotFoundException {
+ NetworkingClient client = this.networkClients.get(id);
+ if (client == null) {
+ throw new ClientNotFoundException(id);
+ }
+
+ client.closeConnection();
+
}
}
diff --git a/framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java b/framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java
index 76d26d6..74871e4 100644
--- a/framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java
+++ b/framework/src/main/java/org/toop/framework/networking/clients/TournamentNetworkingClient.java
@@ -11,9 +11,12 @@ import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.toop.framework.networking.exceptions.CouldNotConnectException;
import org.toop.framework.networking.handlers.NetworkingGameClientHandler;
import org.toop.framework.networking.interfaces.NetworkingClient;
+import java.net.InetSocketAddress;
+
public class TournamentNetworkingClient implements NetworkingClient {
private static final Logger logger = LogManager.getLogger(TournamentNetworkingClient.class);
private Channel channel;
@@ -21,7 +24,12 @@ public class TournamentNetworkingClient implements NetworkingClient {
public TournamentNetworkingClient() {}
@Override
- public boolean connect(long clientId, String host, int port) {
+ public InetSocketAddress getAddress() {
+ return (InetSocketAddress) channel.remoteAddress();
+ }
+
+ @Override
+ public void connect(long clientId, String host, int port) throws CouldNotConnectException {
try {
Bootstrap bootstrap = new Bootstrap();
EventLoopGroup workerGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
@@ -44,11 +52,9 @@ public class TournamentNetworkingClient implements NetworkingClient {
});
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
this.channel = channelFuture.channel();
- } catch (Exception e) {
- logger.error("Failed to create networking client instance", e);
- return false;
+ } catch (Exception _) {
+ throw new CouldNotConnectException(clientId);
}
- return true;
}
@Override
diff --git a/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java b/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java
index 63fb7fc..e2281bd 100644
--- a/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java
+++ b/framework/src/main/java/org/toop/framework/networking/events/NetworkEvents.java
@@ -9,6 +9,7 @@ import org.toop.framework.eventbus.events.UniqueEvent;
import org.toop.framework.eventbus.events.EventsBase;
import org.toop.annotations.AutoResponseResult;
import org.toop.framework.networking.interfaces.NetworkingClient;
+import org.toop.framework.networking.types.NetworkingReconnect;
/**
* A collection of networking-related event records for use with the {@link
@@ -101,31 +102,6 @@ public class NetworkEvents extends EventsBase {
/** Request to close a specific client connection. */
public record CloseClient(long clientId) implements GenericEvent {}
- /**
- * Event to start a new client connection.
- *
- * Carries IP, port, and a unique event ID for correlation with responses.
- *
- * @param networkingClientClass The type of networking client to create.
- * @param host Server IP address.
- * @param port Server port.
- * @param eventSnowflake Unique event identifier for correlation.
- */
- public record StartClient(
- NetworkingClient networkingClientClass,
- String host,
- int port,
- long identifier) implements UniqueEvent {}
-
- /**
- * Response confirming a client was started.
- *
- * @param clientId The client ID assigned to the new connection.
- * @param identifier Event ID used for correlation.
- */
- @AutoResponseResult
- public record StartClientResponse(long clientId, long identifier) implements ResponseToUniqueEvent {}
-
/** Generic server response. */
public record ServerResponse(long clientId) implements GenericEvent {}
@@ -137,8 +113,49 @@ public class NetworkEvents extends EventsBase {
*/
public record SendCommand(long clientId, String... args) implements GenericEvent {}
+ /**
+ * Event to start a new client connection.
+ *
+ *
Carries IP, port, and a unique event ID for correlation with responses.
+ *
+ * @param networkingClientClass The type of networking client to create.
+ * @param host Server IP address.
+ * @param port Server port.
+ * @param identifier Unique event identifier for correlation.
+ */
+ public record StartClient(
+ NetworkingClient networkingClientClass,
+ String host,
+ int port,
+ NetworkingReconnect networkingReconnect,
+ long identifier) implements UniqueEvent {}
+
+ /**
+ * Response confirming a client was started.
+ *
+ * @param clientId The client ID assigned to the new connection.
+ * @param identifier Event ID used for correlation.
+ */
+ @AutoResponseResult
+ public record StartClientResponse(long clientId, long identifier) implements ResponseToUniqueEvent {}
+
/** WIP (Not working) Request to reconnect a client to a previous address. */
- public record Reconnect(long clientId) implements GenericEvent {}
+ public record Reconnect(long clientId, NetworkingReconnect networkingReconnect, long identifier)
+ implements UniqueEvent {}
+
+ public record ReconnectResponse(boolean successful, long identifier) implements ResponseToUniqueEvent {}
+
+ /**
+ * Request to change a client connection to a new server.
+ *
+ * @param clientId The client connection ID.
+ * @param ip The new server IP.
+ * @param port The new server port.
+ */
+ public record ChangeAddress(long clientId, String ip, int port, NetworkingReconnect networkingReconnect, long identifier)
+ implements UniqueEvent {}
+
+ public record ChangeAddressResponse(boolean successful, long identifier) implements ResponseToUniqueEvent {}
/**
* Response triggered when a message is received from a server.
@@ -148,18 +165,6 @@ public class NetworkEvents extends EventsBase {
*/
public record ReceivedMessage(long clientId, String message) implements GenericEvent {}
- /**
- * Request to change a client connection to a new server.
- *
- * @param clientId The client connection ID.
- * @param ip The new server IP.
- * @param port The new server port.
- */
- public record ChangeClientHost(long clientId, String ip, int port) implements GenericEvent {}
-
- /** WIP (Not working) Response indicating that the client could not connect. */
- public record CouldNotConnect(long clientId) implements GenericEvent {}
-
/** Event indicating a client connection was closed. */
public record ClosedConnection(long clientId) implements GenericEvent {}
}
diff --git a/framework/src/main/java/org/toop/framework/networking/exceptions/ClientNotFoundException.java b/framework/src/main/java/org/toop/framework/networking/exceptions/ClientNotFoundException.java
new file mode 100644
index 0000000..2506b26
--- /dev/null
+++ b/framework/src/main/java/org/toop/framework/networking/exceptions/ClientNotFoundException.java
@@ -0,0 +1,25 @@
+package org.toop.framework.networking.exceptions;
+
+/**
+ * Thrown when an operation is attempted on a networking client
+ * that does not exist or has already been closed.
+ */
+public class ClientNotFoundException extends RuntimeException {
+
+ private final long clientId;
+
+ public ClientNotFoundException(long clientId) {
+ super("Networking client with ID " + clientId + " was not found.");
+ this.clientId = clientId;
+ }
+
+ public ClientNotFoundException(long clientId, Throwable cause) {
+ super("Networking client with ID " + clientId + " was not found.", cause);
+ this.clientId = clientId;
+ }
+
+ public long getClientId() {
+ return clientId;
+ }
+
+}
\ No newline at end of file
diff --git a/framework/src/main/java/org/toop/framework/networking/exceptions/CouldNotConnectException.java b/framework/src/main/java/org/toop/framework/networking/exceptions/CouldNotConnectException.java
new file mode 100644
index 0000000..839fb0b
--- /dev/null
+++ b/framework/src/main/java/org/toop/framework/networking/exceptions/CouldNotConnectException.java
@@ -0,0 +1,21 @@
+package org.toop.framework.networking.exceptions;
+
+public class CouldNotConnectException extends RuntimeException {
+
+ private final long clientId;
+
+ public CouldNotConnectException(long clientId) {
+ super("Networking client with ID " + clientId + " could not connect.");
+ this.clientId = clientId;
+ }
+
+ public CouldNotConnectException(long clientId, Throwable cause) {
+ super("Networking client with ID " + clientId + " could not connect.", cause);
+ this.clientId = clientId;
+ }
+
+ public long getClientId() {
+ return clientId;
+ }
+
+}
\ No newline at end of file
diff --git a/framework/src/main/java/org/toop/framework/networking/NetworkingInitializationException.java b/framework/src/main/java/org/toop/framework/networking/exceptions/NetworkingInitializationException.java
similarity index 79%
rename from framework/src/main/java/org/toop/framework/networking/NetworkingInitializationException.java
rename to framework/src/main/java/org/toop/framework/networking/exceptions/NetworkingInitializationException.java
index d9081d1..0ff430a 100644
--- a/framework/src/main/java/org/toop/framework/networking/NetworkingInitializationException.java
+++ b/framework/src/main/java/org/toop/framework/networking/exceptions/NetworkingInitializationException.java
@@ -1,4 +1,4 @@
-package org.toop.framework.networking;
+package org.toop.framework.networking.exceptions;
public class NetworkingInitializationException extends RuntimeException {
public NetworkingInitializationException(String message, Throwable cause) {
diff --git a/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClient.java b/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClient.java
index 6cca1e9..09b215c 100644
--- a/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClient.java
+++ b/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClient.java
@@ -1,7 +1,12 @@
package org.toop.framework.networking.interfaces;
+import org.toop.framework.networking.exceptions.CouldNotConnectException;
+
+import java.net.InetSocketAddress;
+
public interface NetworkingClient {
- boolean connect(long clientId, String host, int port);
+ InetSocketAddress getAddress();
+ void connect(long clientId, String host, int port) throws CouldNotConnectException;
boolean isActive();
void writeAndFlush(String msg);
void closeConnection();
diff --git a/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java b/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java
index 0a2d70d..b9d8445 100644
--- a/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java
+++ b/framework/src/main/java/org/toop/framework/networking/interfaces/NetworkingClientManager.java
@@ -1,11 +1,20 @@
package org.toop.framework.networking.interfaces;
-import java.util.OptionalLong;
+import org.toop.framework.networking.exceptions.ClientNotFoundException;
+import org.toop.framework.networking.exceptions.CouldNotConnectException;
+import org.toop.framework.networking.types.NetworkingReconnect;
+
+import java.util.Optional;
public interface NetworkingClientManager {
- OptionalLong startClient(long id, NetworkingClient networkingClientClass, String host, int port);
- boolean sendCommand(long id, String command);
- boolean reconnect(long id);
- boolean changeAddress(long id, String host, int port);
- boolean closeClient(long id);
+ void startClient(
+ long id,
+ NetworkingClient networkingClientClass,
+ String host,
+ int port,
+ NetworkingReconnect networkingReconnect) throws CouldNotConnectException;
+ void sendCommand(long id, String command) throws ClientNotFoundException;
+ void reconnect(long id, NetworkingReconnect networkingReconnect) throws ClientNotFoundException;
+ void changeAddress(long id, String host, int port, NetworkingReconnect networkingReconnect) throws ClientNotFoundException;
+ void closeClient(long id) throws ClientNotFoundException;
}
diff --git a/framework/src/main/java/org/toop/framework/networking/types/NetworkingReconnect.java b/framework/src/main/java/org/toop/framework/networking/types/NetworkingReconnect.java
new file mode 100644
index 0000000..7245124
--- /dev/null
+++ b/framework/src/main/java/org/toop/framework/networking/types/NetworkingReconnect.java
@@ -0,0 +1,5 @@
+package org.toop.framework.networking.types;
+
+import java.util.concurrent.TimeUnit;
+
+public record NetworkingReconnect(int reconnectAttempts, long timeout, TimeUnit timeUnit) {}
\ No newline at end of file