Add: complete JavaDoc for MiniModel and TCPServer

This commit is contained in:
MatteoPellegrino05
2026-05-18 18:59:31 +02:00
parent e28e7bab6d
commit 4434053138
2 changed files with 376 additions and 125 deletions
@@ -18,33 +18,96 @@ import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
/**
* Server TCP. Accepts connections and manages all client handlers.
* TCP server responsible for accepting client connections,
* handling player registration and reconnection, and sending
* game updates to connected clients.
*
* <p>The server also manages a dedicated heartbeat channel used
* to detect disconnected clients and associate each heartbeat
* connection with the corresponding {@link ClientHandler}.
*/
public class TCPServer {
/**
* Main TCP port used for standard client-server communication.
*/
int port;
int heartbeatPort; // ← nuova porta
/**
* TCP port dedicated to heartbeat communication.
*/
int heartbeatPort;
/**
* Number of players that have successfully connected.
*/
int connectedPlayers;
ServerSocket socketTCP;
ServerSocket heartbeatSocketTCP; // ← nuovo ServerSocket
/**
* Main server socket used to accept client connections.
*/
ServerSocket socketTCP;
/**
* Server socket used to accept heartbeat connections.
*/
ServerSocket heartbeatSocketTCP;
/**
* Game controller used to manage the server-side game logic.
*/
final GameController controller;
/**
* Queue containing network events received from clients.
*/
BlockingQueue<NetworkEvent> actionQueue;
/**
* Map storing the online/offline status of connected players.
*/
LimitedMap<String, Boolean> playerList;
/**
* List of active TCP client handlers.
*/
List<ClientHandler> clientHandlers;
/**
* Flag indicating whether the server is recovering from a previous crash.
*/
boolean serverCrashed;
/**
* Sets whether the server is recovering from a previous crash.
*
* @param serverCrashed {@code true} if the server is in crash-recovery mode,
* {@code false} otherwise.
*/
public void setServerCrashed(boolean serverCrashed) {
this.serverCrashed = serverCrashed;
}
// Mappa temporanea: username → ClientHandler
// Serve per associare il socket heartbeat al giusto ClientHandler
/**
* Temporary map associating each username with the corresponding
* {@link ClientHandler} waiting for its heartbeat connection.
*/
private final Map<String, ClientHandler> pendingHeartbeat = new ConcurrentHashMap<>();
public TCPServer(GameController controller, int port, int heartbeatPort,
BlockingQueue<NetworkEvent> actionQueue, LimitedMap<String, Boolean> playerList) {
/**
* Constructs a TCP server with the required game and network components.
*
* @param controller the game controller used to manage the game logic.
* @param port the main TCP port used for client communication.
* @param heartbeatPort the TCP port dedicated to heartbeat connections.
* @param actionQueue the queue containing incoming network events.
* @param playerList the map storing the connection status of the players.
*/
public TCPServer(GameController controller,
int port,
int heartbeatPort,
BlockingQueue<NetworkEvent> actionQueue,
LimitedMap<String, Boolean> playerList) {
this.port = port;
this.heartbeatPort = heartbeatPort;
this.connectedPlayers = 0;
@@ -54,8 +117,24 @@ public class TCPServer {
this.clientHandlers = new ArrayList<>();
}
/**
* Starts the TCP server and begins accepting client connections.
*
* <p>The method opens both the main TCP server socket and the dedicated
* heartbeat server socket. It then starts a separate thread for heartbeat
* connections and continuously waits for new players or reconnecting clients.
*
* <p>When a new connection is received, the first event must be an
* {@link AddPlayer} request. Depending on the current server state,
* the connection is handled either as a new player joining the game
* or as a reconnection attempt.
*
* @param serverCrashed {@code true} if the server is being restarted after a crash,
* {@code false} otherwise.
*/
public void start(boolean serverCrashed) {
this.serverCrashed = serverCrashed;
try {
socketTCP = new ServerSocket(port);
heartbeatSocketTCP = new ServerSocket(heartbeatPort);
@@ -68,16 +147,19 @@ public class TCPServer {
System.out.println("TCP server started on port: " + port);
System.out.println("Heartbeat server started on port: " + heartbeatPort);
// Thread separato per accettare le connessioni heartbeat
new Thread(this::acceptHeartbeat, "heartbeat-acceptor").start();
// Loop principale — invariato nella logica, cambia solo la creazione del ClientHandler
while (true) {
try {
Socket clientSocket = socketTCP.accept();
ObjectOutputStream clientSend = new ObjectOutputStream(clientSocket.getOutputStream());
ObjectInputStream clientReceive = new ObjectInputStream(clientSocket.getInputStream());
ObjectOutputStream clientSend =
new ObjectOutputStream(clientSocket.getOutputStream());
ObjectInputStream clientReceive =
new ObjectInputStream(clientSocket.getInputStream());
NetworkEvent event = (NetworkEvent) clientReceive.readObject();
if (!(event.getEventType() == EventType.ADD_PLAYER)) {
clientSocket.getOutputStream().write(-1);
clientSocket.close();
@@ -87,136 +169,195 @@ public class TCPServer {
AddPlayer eventAddPlayer = (AddPlayer) event;
if (eventAddPlayer.getProposedNPlayer() < 2 || eventAddPlayer.getProposedNPlayer() > 5) {
if (eventAddPlayer.getProposedNPlayer() < 2
|| eventAddPlayer.getProposedNPlayer() > 5) {
clientSocket.getOutputStream().write(-1);
clientSocket.close();
System.out.println("Invalid parameters. Connection terminated.");
continue;
}
synchronized (controller) {
synchronized (controller) {
String username = eventAddPlayer.getUsername();
String username = eventAddPlayer.getUsername();
if(serverCrashed){
if(controller.getModel().getPlayers().stream().anyMatch(p -> p.getUserName().equals(username))&& !playerList.containsKey(username)){
playerList.put(username, true);
System.out.println("(After crash)Reconnected player: " + username);
ClientHandler handler = new ClientHandler(
username, clientSocket, clientSend, clientReceive,
clientHandlers,playerList, actionQueue
);
clientSocket.getOutputStream().write(1);
pendingHeartbeat.put(username, handler);
Thread thread = new Thread(handler);
thread.start();
clientHandlers.add(handler);
connectedPlayers++;
}
else if(playerList.containsKey(username) && !playerList.get(username)){
// riconnessione
playerList.put(username, true);
System.out.println("Reconnected player: " + username);
if (serverCrashed) {
if (controller.getModel().getPlayers().stream()
.anyMatch(p -> p.getUserName().equals(username))
&& !playerList.containsKey(username)) {
ClientHandler handler = new ClientHandler(
username, clientSocket, clientSend, clientReceive,
clientHandlers, playerList, actionQueue
);
clientSocket.getOutputStream().write(1);
pendingHeartbeat.put(username, handler);
Game game = controller.getModel();
handler.notifyMiniModel(new MiniModel(game.getBoard(),game.getSlotMap(), game.orderLogicCard,game.getCurrentState(),game.getPlayers(),game.getAvailableTotems()));
Thread thread = new Thread(handler);
thread.start();
clientHandlers.add(handler);
connectedPlayers++;
actionQueue.add(new ReconnectPlayer(username));
}
else
{
clientSocket.getOutputStream().write(-1);
clientSocket.close();
System.out.println("Player could not be added. Connection terminated.");
}
}
else
{
if (playerList.isEmpty()) {
Game model = new Game(eventAddPlayer.getProposedNPlayer());
controller.setModel(model);
playerList.setLimit(eventAddPlayer.getProposedNPlayer());
}
if (controller.addPlayer(username)) {
// nuovo giocatore
playerList.put(username, true);
System.out.println("Accepted player: " + username);
ClientHandler handler = new ClientHandler(
username, clientSocket, clientSend, clientReceive,
clientHandlers,playerList, actionQueue
);
clientSocket.getOutputStream().write(1);
pendingHeartbeat.put(username, handler);
Thread thread = new Thread(handler);
thread.start();
clientHandlers.add(handler);
connectedPlayers++;
// metti in attesa del socket heartbeat
}
else if(playerList.containsKey(username) && !playerList.get(username)){
// riconnessione
playerList.put(username, true);
System.out.println("Reconnected player: " + username);
playerList.put(username, true);
System.out.println("(After crash)Reconnected player: " + username);
ClientHandler handler = new ClientHandler(
username, clientSocket, clientSend, clientReceive,
clientHandlers, playerList, actionQueue
);
clientSocket.getOutputStream().write(1);
pendingHeartbeat.put(username, handler);
Game game = controller.getModel();
handler.notifyMiniModel(new MiniModel(game.getBoard(),game.getSlotMap(), game.orderLogicCard,game.getCurrentState(),game.getPlayers(),game.getAvailableTotems()));
Thread thread = new Thread(handler);
thread.start();
clientHandlers.add(handler);
connectedPlayers++;
actionQueue.add(new ReconnectPlayer(username));
}
else{
clientSocket.getOutputStream().write(-1);
clientSocket.close();
System.out.println("Player could not be added. Connection terminated.");
}
}
ClientHandler handler = new ClientHandler(
username,
clientSocket,
clientSend,
clientReceive,
clientHandlers,
playerList,
actionQueue
);
}
clientSocket.getOutputStream().write(1);
pendingHeartbeat.put(username, handler);
Thread thread = new Thread(handler);
thread.start();
}
catch(IOException | ClassNotFoundException e){
clientHandlers.add(handler);
connectedPlayers++;
} else if (playerList.containsKey(username)
&& !playerList.get(username)) {
playerList.put(username, true);
System.out.println("Reconnected player: " + username);
ClientHandler handler = new ClientHandler(
username,
clientSocket,
clientSend,
clientReceive,
clientHandlers,
playerList,
actionQueue
);
clientSocket.getOutputStream().write(1);
pendingHeartbeat.put(username, handler);
Game game = controller.getModel();
handler.notifyMiniModel(new MiniModel(
game.getBoard(),
game.getSlotMap(),
game.orderLogicCard,
game.getCurrentState(),
game.getPlayers(),
game.getAvailableTotems()
));
Thread thread = new Thread(handler);
thread.start();
clientHandlers.add(handler);
connectedPlayers++;
actionQueue.add(new ReconnectPlayer(username));
} else {
clientSocket.getOutputStream().write(-1);
clientSocket.close();
System.out.println("Player could not be added. Connection terminated.");
}
} else {
if (playerList.isEmpty()) {
Game model = new Game(eventAddPlayer.getProposedNPlayer());
controller.setModel(model);
playerList.setLimit(eventAddPlayer.getProposedNPlayer());
}
if (controller.addPlayer(username)) {
playerList.put(username, true);
System.out.println("Accepted player: " + username);
ClientHandler handler = new ClientHandler(
username,
clientSocket,
clientSend,
clientReceive,
clientHandlers,
playerList,
actionQueue
);
clientSocket.getOutputStream().write(1);
pendingHeartbeat.put(username, handler);
Thread thread = new Thread(handler);
thread.start();
clientHandlers.add(handler);
connectedPlayers++;
} else if (playerList.containsKey(username)
&& !playerList.get(username)) {
playerList.put(username, true);
System.out.println("Reconnected player: " + username);
ClientHandler handler = new ClientHandler(
username,
clientSocket,
clientSend,
clientReceive,
clientHandlers,
playerList,
actionQueue
);
clientSocket.getOutputStream().write(1);
pendingHeartbeat.put(username, handler);
Game game = controller.getModel();
handler.notifyMiniModel(new MiniModel(
game.getBoard(),
game.getSlotMap(),
game.orderLogicCard,
game.getCurrentState(),
game.getPlayers(),
game.getAvailableTotems()
));
Thread thread = new Thread(handler);
thread.start();
clientHandlers.add(handler);
connectedPlayers++;
actionQueue.add(new ReconnectPlayer(username));
} else {
clientSocket.getOutputStream().write(-1);
clientSocket.close();
System.out.println("Player could not be added. Connection terminated.");
}
}
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
/**
* Accetta connessioni sul socket heartbeat e le associa al ClientHandler giusto.
* Il client manda subito il proprio username per identificarsi.
* Accepts heartbeat connections and associates them with the correct client handler.
*
* <p>Each client immediately sends its username on the heartbeat channel.
* The method uses that username to retrieve the pending {@link ClientHandler}
* and starts a dedicated {@link HeartbeatHandler}. If no pending handler is found,
* the heartbeat socket is closed.
*/
private void acceptHeartbeat() {
while (true) {
try {
Socket hbSocket = heartbeatSocketTCP.accept();
ObjectInputStream hbIn = new ObjectInputStream(hbSocket.getInputStream());
ObjectInputStream hbIn =
new ObjectInputStream(hbSocket.getInputStream());
// il client manda subito il suo username
String username = (String) hbIn.readObject();
ClientHandler handler = pendingHeartbeat.remove(username);
if (handler != null) {
HeartbeatHandler hb = new HeartbeatHandler(username, hbSocket, handler);
HeartbeatHandler hb =
new HeartbeatHandler(username, hbSocket, handler);
new Thread(hb, "heartbeat-" + username).start();
System.out.println("Heartbeat connected for: " + username);
} else {
System.out.println("No pending handler for: " + username + ", closing heartbeat.");
System.out.println(
"No pending handler for: " + username + ", closing heartbeat."
);
hbSocket.close();
}
@@ -226,13 +367,27 @@ public class TCPServer {
}
}
/**
* Notifies connected TCP clients of a new network event.
*
* <p>If the event does not represent an error, it is sent to all connected clients.
* If it represents an error, it is sent only to the client that requested the action.
*
* @param event the network event to send to the clients.
*/
public void notifyAll(NetworkEvent event) {
clientHandlers.forEach(h -> {
if (!event.getIsError() || event.getUsername().equals(h.getUsername()))
if (!event.getIsError() || event.getUsername().equals(h.getUsername())) {
h.notifyEvent(event);
}
});
}
/**
* Notifies all connected TCP clients of a new game model.
*
* @param model the updated mini model to send to the clients.
*/
public void notifyAll(MiniModel model) {
clientHandlers.forEach(h -> h.notifyMiniModel(model));
}