Add: complete JavaDoc for MiniModel and TCPServer
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user