Merge branch 'FA' into server-resilance
This commit is contained in:
+3
@@ -1,6 +1,9 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
<component name="InspectionProjectProfileManager">
|
||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,java.lang.foreign.Arena,ofAuto,java.lang.foreign.Arena,global,java.util.concurrent.Executors,newSingleThreadExecutor" />
|
||||||
|
</inspection_tool>
|
||||||
<inspection_tool class="MissingJavadoc" enabled="true" level="WARNING" enabled_by_default="true" />
|
<inspection_tool class="MissingJavadoc" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
||||||
@@ -71,7 +71,13 @@ public class Game implements Serializable {
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return disconnetedPlayers.put(player,false);
|
disconnetedPlayers.put(player,false);
|
||||||
|
if(currentState.getGameStage().equals(GameStages.SLOT_CHOICE))
|
||||||
|
{
|
||||||
|
disconnetedPlayers.remove(player);
|
||||||
|
orderLogicCard.pushNoEffect(player);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Returns the current number of players participating in the game.
|
* Returns the current number of players participating in the game.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.rmi.RemoteException;
|
|||||||
import java.rmi.registry.LocateRegistry;
|
import java.rmi.registry.LocateRegistry;
|
||||||
import java.rmi.registry.Registry;
|
import java.rmi.registry.Registry;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
import it.polimi.ingsw.gc14.Controller.ClientController;
|
import it.polimi.ingsw.gc14.Controller.ClientController;
|
||||||
import it.polimi.ingsw.gc14.Network.IClient;
|
import it.polimi.ingsw.gc14.Network.IClient;
|
||||||
@@ -18,33 +19,24 @@ import it.polimi.ingsw.gc14.Network.RMI.Server.RMIServer;
|
|||||||
*/
|
*/
|
||||||
public class RMIClient implements IClient {
|
public class RMIClient implements IClient {
|
||||||
|
|
||||||
/** The host address of the RMI server */
|
private static final long PING_INTERVAL_S = 3; // mirrors TCPClient 3 s
|
||||||
|
private static final long PING_TIMEOUT_MS = 5_000; // mirrors SILENCE_THRESHOLD_MS
|
||||||
|
|
||||||
private final String host;
|
private final String host;
|
||||||
|
|
||||||
/** The port of the RMI server */
|
|
||||||
private final int port;
|
private final int port;
|
||||||
|
|
||||||
/** The remote stub used to call methods on the server */
|
|
||||||
private IGameServer stub;
|
private IGameServer stub;
|
||||||
|
private ClientController controller;
|
||||||
/** Client game's controller */
|
|
||||||
ClientController controller;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Local IP address of the RMI client.
|
|
||||||
*/
|
|
||||||
private String myIP;
|
private String myIP;
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
private volatile boolean running = false;
|
||||||
|
|
||||||
|
/** Scheduler that fires ping() every PING_INTERVAL_S seconds. */
|
||||||
|
private ScheduledExecutorService pingSender;
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class constructor.
|
|
||||||
*
|
|
||||||
* @param controller the client controller used to create the callback.
|
|
||||||
* @param host the host address of the RMI server.
|
|
||||||
* @param port the port of the RMI server.
|
|
||||||
* @param myIP the local IP address used by the RMI client.
|
|
||||||
*/
|
|
||||||
public RMIClient(ClientController controller, String host, int port, String myIP) {
|
public RMIClient(ClientController controller, String host, int port, String myIP) {
|
||||||
this.controller=controller;
|
this.controller = controller;
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.myIP = myIP;
|
this.myIP = myIP;
|
||||||
@@ -52,29 +44,89 @@ public class RMIClient implements IClient {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connects to the RMI server and attempts to join the game.
|
* Connects to the RMI server and starts the heartbeat loop.
|
||||||
* Looks up the RMI registry to retrieve the {@link IGameServer} stub.
|
*
|
||||||
* Then, creates a {@link ClientCallbackImpl} and calls {@link RMIServer#joinGame(String, int, IClientCallback)}.
|
* <p>Mirrors {@code TCPClient.connect()}: after a successful join the
|
||||||
* @param username the player's username
|
* heartbeat channel is opened (here: a scheduler is started instead of
|
||||||
* @param preferredInt the desired number of players
|
* opening a second socket).
|
||||||
* @return true if the player successfully joined the game, false otherwise
|
|
||||||
*/
|
*/
|
||||||
public boolean connect(String username,int preferredInt) {
|
@Override
|
||||||
|
public boolean connect(String username, int preferredInt) {
|
||||||
try {
|
try {
|
||||||
System.setProperty("java.rmi.server.hostname", this.myIP);
|
System.setProperty("java.rmi.server.hostname", this.myIP);
|
||||||
Registry registry = LocateRegistry.getRegistry(host, port);
|
Registry registry = LocateRegistry.getRegistry(host, port);
|
||||||
this.stub = (IGameServer) registry.lookup("RMIGameServer");
|
this.stub = (IGameServer) registry.lookup("RMIGameServer");
|
||||||
ClientCallbackImpl callback = new ClientCallbackImpl(controller);
|
this.username = username;
|
||||||
|
|
||||||
return stub.joinGame(username, preferredInt, callback);
|
ClientCallbackImpl callback = new ClientCallbackImpl(controller);
|
||||||
}
|
boolean joined = stub.joinGame(username, preferredInt, callback);
|
||||||
catch (Exception e) {
|
if (!joined) return false;
|
||||||
|
|
||||||
|
running = true;
|
||||||
|
startHeartbeat();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Heartbeat — mirrors TCPClient.heartbeatLoop()
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts sending periodic pings to the server.
|
||||||
|
*
|
||||||
|
* <p>Mirrors the {@code ScheduledExecutorService} in
|
||||||
|
* {@code TCPClient.heartbeatLoop()} that writes {@code PING} every 3 s.
|
||||||
|
* On {@link RemoteException} the server is considered gone and
|
||||||
|
* {@link #disconnect()} is called — mirrors the behaviour on
|
||||||
|
* {@code SocketTimeoutException} / {@code IOException} in the TCP version.
|
||||||
|
*/
|
||||||
|
private void startHeartbeat() {
|
||||||
|
pingSender = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||||
|
Thread t = new Thread(r, "rmi-heartbeat");
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
pingSender.scheduleAtFixedRate(() -> {
|
||||||
|
Future<?> future = executor.submit(() -> {
|
||||||
|
try {
|
||||||
|
stub.ping(username);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
future.get(PING_TIMEOUT_MS, TimeUnit.MILLISECONDS); // mirrors setSoTimeout(5000)
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
future.cancel(true);
|
||||||
|
System.out.println("RMI ping timeout: " + username);
|
||||||
|
disconnect();
|
||||||
|
} catch (Exception e) {
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}, 0, PING_INTERVAL_S, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tears down the connection.
|
||||||
|
* Mirrors {@code TCPClient.disconnect()}: stops the heartbeat and notifies
|
||||||
|
* the view.
|
||||||
|
*/
|
||||||
|
private void disconnect() {
|
||||||
|
if (!running) return;
|
||||||
|
running = false;
|
||||||
|
if (pingSender != null) pingSender.shutdownNow();
|
||||||
|
controller.view.showError("Connessione al server persa");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests to draw a tribe card from the upper list.
|
* Requests to draw a tribe card from the upper list.
|
||||||
|
|||||||
@@ -9,47 +9,26 @@ import java.rmi.*;
|
|||||||
*/
|
*/
|
||||||
public interface IGameServer extends Remote {
|
public interface IGameServer extends Remote {
|
||||||
|
|
||||||
/**
|
boolean joinGame(String username, int preferredInt, IClientCallback callback) throws RemoteException;
|
||||||
* Adds a player to the game through the remote server.
|
|
||||||
*
|
|
||||||
* @param username the username of the player joining the game.
|
|
||||||
* @param preferredInt the preferred player number or slot selected by the client.
|
|
||||||
* @param callback the client callback used by the server to send updates.
|
|
||||||
* @return {@code true} if the player successfully joins the game;
|
|
||||||
* {@code false} otherwise.
|
|
||||||
* @throws RemoteException if an RMI communication error occurs.
|
|
||||||
*/
|
|
||||||
boolean joinGame(String username,int preferredInt, IClientCallback callback) throws RemoteException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a network event to the game server.
|
|
||||||
*
|
|
||||||
* @param event the event to be processed by the server.
|
|
||||||
* @return {@code true} if the event is accepted and processed;
|
|
||||||
* {@code false} otherwise.
|
|
||||||
* @throws RemoteException if an RMI communication error occurs.
|
|
||||||
*/
|
|
||||||
boolean doEvent(NetworkEvent event) throws RemoteException;
|
boolean doEvent(NetworkEvent event) throws RemoteException;
|
||||||
|
|
||||||
void drawUpperTribeCard(String playerUsername, int pos) throws RemoteException;
|
void drawUpperTribeCard(String playerUsername, int pos) throws RemoteException;
|
||||||
|
void drawLowerTribeCard(String playerUsername, int pos) throws RemoteException;
|
||||||
void drawLowerTribeCard(String playerUsername,int pos) throws RemoteException;
|
void drawUpperBuildingCard(String playerUsername, int pos) throws RemoteException;
|
||||||
|
void drawLowerBuildingCard(String playerUsername, int pos) throws RemoteException;
|
||||||
void drawUpperBuildingCard(String playerUsername,int pos) throws RemoteException;
|
|
||||||
|
|
||||||
|
|
||||||
void drawLowerBuildingCard(String playerUsername,int pos) throws RemoteException;
|
|
||||||
|
|
||||||
void skipTurn(String playerUsername) throws RemoteException;
|
void skipTurn(String playerUsername) throws RemoteException;
|
||||||
|
void pickOptionalTribeCard(String playerUsername, int pos) throws RemoteException;
|
||||||
|
void pickOptionalBuildingCard(String playerUsername, int pos) throws RemoteException;
|
||||||
|
|
||||||
void pickOptionalTribeCard(String playerUsername,int pos) throws RemoteException;
|
|
||||||
|
|
||||||
void pickOptionalBuildingCard(String playerUsername,int pos) throws RemoteException;
|
|
||||||
|
|
||||||
|
|
||||||
void noOptionalCard(String playerUsername) throws RemoteException;
|
void noOptionalCard(String playerUsername) throws RemoteException;
|
||||||
|
void slotChoice(String playerUsername, int pos) throws RemoteException;
|
||||||
|
|
||||||
void slotChoice(String playerUsername,int pos) throws RemoteException;
|
/**
|
||||||
|
* Heartbeat: called periodically by the client to signal it is still alive.
|
||||||
}
|
* Mirrors the PING/PONG mechanism used in the TCP heartbeat channel.
|
||||||
|
*
|
||||||
|
* @param username the username of the client sending the ping.
|
||||||
|
* @throws RemoteException if an RMI communication error occurs.
|
||||||
|
*/
|
||||||
|
void ping(String username) throws RemoteException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package it.polimi.ingsw.gc14.Network.RMI.Server;
|
||||||
|
|
||||||
|
import it.polimi.ingsw.gc14.LimitedMap;
|
||||||
|
import it.polimi.ingsw.gc14.Model.Game;
|
||||||
|
import it.polimi.ingsw.gc14.Network.NetworkEvent;
|
||||||
|
import it.polimi.ingsw.gc14.Network.NetworkEvents.SkipPlayerDisconnected;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server-side heartbeat watchdog for a single RMI client.
|
||||||
|
*
|
||||||
|
* <p>Mirrors {@code HeartbeatHandler} used in the TCP stack, but adapted for RMI:
|
||||||
|
* instead of reading raw bytes from a dedicated socket, it relies on {@link #receivePing()}
|
||||||
|
* being called by {@link RMIServer#ping(String)} every time the client sends a ping.
|
||||||
|
*
|
||||||
|
* <p>If no ping is received within {@value SILENCE_THRESHOLD_MS} ms the player is
|
||||||
|
* considered disconnected and {@link #disconnect()} is invoked, which:
|
||||||
|
* <ul>
|
||||||
|
* <li>stops the watchdog;</li>
|
||||||
|
* <li>marks the player as offline in {@code playerList};</li>
|
||||||
|
* <li>removes the callback from {@code clients};</li>
|
||||||
|
* <li>optionally pushes a {@link SkipPlayerDisconnected} event if it was that
|
||||||
|
* player's turn.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public class RMIHeartbeat {
|
||||||
|
|
||||||
|
private static final long SILENCE_THRESHOLD_MS = 5_000;
|
||||||
|
|
||||||
|
private String username = "";
|
||||||
|
private final LimitedMap<String, Boolean> playerList;
|
||||||
|
private final Map<String, ?> clients; // ConcurrentHashMap<String, IClientCallback>
|
||||||
|
private final BlockingQueue<NetworkEvent> actionQueue;
|
||||||
|
|
||||||
|
/** Last time a ping was received from this client. */
|
||||||
|
private volatile long lastPingTime = System.currentTimeMillis();
|
||||||
|
private volatile boolean running = true;
|
||||||
|
|
||||||
|
/** Reference to the current game model — needed to check whose turn it is. */
|
||||||
|
private volatile Game game;
|
||||||
|
|
||||||
|
private final ScheduledExecutorService watchdog =
|
||||||
|
Executors.newSingleThreadScheduledExecutor(r -> {
|
||||||
|
Thread t = new Thread(r, "rmi-watchdog-" + username);
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
|
||||||
|
public RMIHeartbeat(
|
||||||
|
String username,
|
||||||
|
LimitedMap<String, Boolean> playerList,
|
||||||
|
Map<String, ?> clients,
|
||||||
|
BlockingQueue<NetworkEvent> actionQueue) {
|
||||||
|
|
||||||
|
this.username = username;
|
||||||
|
this.playerList = playerList;
|
||||||
|
this.clients = clients;
|
||||||
|
this.actionQueue = actionQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called by {@link RMIServer} whenever it starts tracking this player. */
|
||||||
|
public void start() {
|
||||||
|
watchdog.scheduleAtFixedRate(() -> {
|
||||||
|
if (System.currentTimeMillis() - lastPingTime > SILENCE_THRESHOLD_MS) {
|
||||||
|
System.out.println("RMI heartbeat timeout: " + username);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}, 1, 1, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by {@link RMIServer#ping(String)} each time the client pings.
|
||||||
|
* Resets the silence timer — mirrors writing {@code lastReceivedTime} in
|
||||||
|
* {@code HeartbeatHandler}.
|
||||||
|
*/
|
||||||
|
public void receivePing() {
|
||||||
|
lastPingTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the server to keep the watchdog up-to-date with the current game
|
||||||
|
* model (needed to check whose turn it is on disconnect).
|
||||||
|
*/
|
||||||
|
public void setGame(Game game) {
|
||||||
|
this.game = game;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private void disconnect() {
|
||||||
|
if (!running) return;
|
||||||
|
running = false;
|
||||||
|
watchdog.shutdownNow();
|
||||||
|
|
||||||
|
// Mark player as offline
|
||||||
|
playerList.put(username, false);
|
||||||
|
|
||||||
|
// Remove RMI callback so notifyAll skips this client
|
||||||
|
clients.remove(username);
|
||||||
|
|
||||||
|
// If it was this player's turn, skip it
|
||||||
|
Game snapshot = this.game;
|
||||||
|
if (snapshot != null &&
|
||||||
|
snapshot.getCurrentState().getCurrentPlayer().getUserName().equals(username)) {
|
||||||
|
actionQueue.add(new SkipPlayerDisconnected(username));
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("RMI disconnected: " + username);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,42 +24,29 @@ import java.rmi.*;
|
|||||||
*/
|
*/
|
||||||
public class RMIServer extends UnicastRemoteObject implements IGameServer {
|
public class RMIServer extends UnicastRemoteObject implements IGameServer {
|
||||||
private String host;
|
private String host;
|
||||||
/** Server game's controller */
|
|
||||||
private GameController controller;
|
private GameController controller;
|
||||||
|
|
||||||
/** Server game's model */
|
|
||||||
private Game model;
|
private Game model;
|
||||||
|
|
||||||
/** RMI registry */
|
|
||||||
private Registry registry;
|
private Registry registry;
|
||||||
|
|
||||||
/** RMI port */
|
|
||||||
private int nPort;
|
private int nPort;
|
||||||
|
|
||||||
/** Map containing the associations between a player's username and its callback */
|
/** username → callback */
|
||||||
private final Map<String, IClientCallback> clients = new ConcurrentHashMap<>();
|
private final Map<String, IClientCallback> clients = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/** Queue containing the events to be applied to the game model */
|
/**
|
||||||
|
* username → watchdog.
|
||||||
|
* One watchdog per connected player, mirrors {@code pendingHeartbeat} / per-socket
|
||||||
|
* HeartbeatHandler in the TCP stack.
|
||||||
|
*/
|
||||||
|
private final Map<String, RMIHeartbeat> watchdogs = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
BlockingQueue<NetworkEvent> actionQueue;
|
BlockingQueue<NetworkEvent> actionQueue;
|
||||||
|
private LimitedMap<String, Boolean> playerList;
|
||||||
/**
|
|
||||||
* List containing the usernames of joined players.
|
|
||||||
* {@link LimitedMap}'s limit defines at which size the list calls its action. The limit can be set using {@link LimitedMap#setLimit(int)}.
|
|
||||||
*/
|
|
||||||
private LimitedMap<String,Boolean> playerList;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
public RMIServer(GameController controller, int nPort,
|
||||||
* Class constructor that initializes the attributes.
|
BlockingQueue<NetworkEvent> actionQueue,
|
||||||
*
|
LimitedMap<String, Boolean> playerList,
|
||||||
* @param controller the game controller.
|
String host) throws RemoteException {
|
||||||
* @param nPort the RMI port.
|
|
||||||
* @param actionQueue the action queue.
|
|
||||||
* @param playerList the players' usernames list.
|
|
||||||
* @param host the host address of the RMI server.
|
|
||||||
* @throws RemoteException if an RMI error occurs.
|
|
||||||
*/
|
|
||||||
public RMIServer(GameController controller, int nPort, BlockingQueue<NetworkEvent> actionQueue, LimitedMap<String,Boolean> playerList, String host) throws RemoteException {
|
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.nPort = nPort;
|
this.nPort = nPort;
|
||||||
this.actionQueue = actionQueue;
|
this.actionQueue = actionQueue;
|
||||||
@@ -67,42 +54,102 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
|
|||||||
this.host = host;
|
this.host = host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Join
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows a player to join the game.
|
* {@inheritDoc}
|
||||||
* If the desired number of player is invalid, the request is rejected.
|
*
|
||||||
* If this is the first player, a new game model is created and passed to the controller. Additionally, the playerList's limit is set.
|
* <p>After a successful join a {@link RMIHeartbeat} is created and
|
||||||
* Then, if the controller successfully adds the player, the username is added to {@link #playerList} and {@link #clients}.
|
* started for the new player — mirrors creating a {@code HeartbeatHandler} in
|
||||||
* @param username The player's name
|
* {@code TCPServer.acceptHeartbeat()}.
|
||||||
* @param preferredInt The desired number of players
|
|
||||||
* @param callback The client's callback interface
|
|
||||||
* @return true if the player successfully joined the game, false otherwise
|
|
||||||
*/
|
*/
|
||||||
public boolean joinGame(String username, int preferredInt, IClientCallback callback) {
|
@Override
|
||||||
if (preferredInt<2 || preferredInt>5) {
|
public boolean joinGame(String username, int preferredInt, IClientCallback callback)
|
||||||
return false;
|
throws RemoteException {
|
||||||
}
|
if (preferredInt < 2 || preferredInt > 5) return false;
|
||||||
synchronized (controller) {
|
|
||||||
if(playerList.isEmpty()){
|
//synchronized (controller) {
|
||||||
if(controller.getModel() == null){
|
if (playerList.isEmpty()) {
|
||||||
model = new Game(preferredInt);
|
model = new Game(preferredInt);
|
||||||
controller.setModel(model);
|
controller.setModel(model);
|
||||||
playerList.setLimit(preferredInt);
|
playerList.setLimit(preferredInt);
|
||||||
}
|
|
||||||
else{
|
|
||||||
model = controller.getModel();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (controller.addPlayer(username)) {
|
if (controller.addPlayer(username)) {
|
||||||
clients.put(username, callback);
|
clients.put(username, callback);
|
||||||
playerList.put(username,true);
|
playerList.put(username, true);
|
||||||
|
startWatchdog(username);
|
||||||
System.out.println("Accepted player: " + username);
|
System.out.println("Accepted player: " + username);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
// Reconnection: player was offline
|
||||||
}
|
if (playerList.containsKey(username) && !playerList.get(username)) {
|
||||||
|
playerList.put(username, true);
|
||||||
|
clients.put(username, callback);
|
||||||
|
System.out.println("Reconnected player: " + username);
|
||||||
|
startWatchdog(username);
|
||||||
|
callback.onGameInit(model);
|
||||||
|
System.out.println("Model sent: " + username);
|
||||||
|
actionQueue.add(new ReconnectPlayer(username));
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Heartbeat — called by RMIClient every ~3 s
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives a heartbeat ping from the client.
|
||||||
|
* Mirrors the server reading {@code PING} and replying {@code PONG} in
|
||||||
|
* {@code HeartbeatHandler.run()}.
|
||||||
|
*
|
||||||
|
* @param username the username of the pinging client.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void ping(String username) throws RemoteException {
|
||||||
|
RMIHeartbeat wd = watchdogs.get(username);
|
||||||
|
if (wd != null) wd.receivePing();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Game model propagation — keep watchdogs in sync
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies all clients of a new event.
|
||||||
|
* Also updates every watchdog with the latest model so disconnect logic
|
||||||
|
* knows whose turn it is.
|
||||||
|
*/
|
||||||
|
public void notifyAll(NetworkEvent action) throws RemoteException {
|
||||||
|
for (Map.Entry<String, IClientCallback> entry : clients.entrySet()) {
|
||||||
|
if (!action.getIsError() ||
|
||||||
|
(action.getIsError() && action.getUsername().equals(entry.getKey()))) {
|
||||||
|
entry.getValue().onAction(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies all clients of a new game model and keeps watchdogs up-to-date.
|
||||||
|
* Mirrors {@code TCPServer.notifyAll(Game)} + the {@code ClientHandler.notifyModel}
|
||||||
|
* call that stores the model for disconnect-turn checking.
|
||||||
|
*/
|
||||||
|
public void notifyAll(Game model) throws RemoteException {
|
||||||
|
this.model = model;
|
||||||
|
// Keep every watchdog's game reference up to date
|
||||||
|
watchdogs.values().forEach(wd -> wd.setGame(model));
|
||||||
|
for (IClientCallback cb : clients.values()) {
|
||||||
|
cb.onGameInit(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Push an action in actionQueue.
|
* Push an action in actionQueue.
|
||||||
@@ -210,35 +257,6 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// RMI's internal methods
|
|
||||||
/**
|
|
||||||
* Sends an action to the RMI clients.
|
|
||||||
*
|
|
||||||
* <p>If the action is an error, it is sent only to the client associated with
|
|
||||||
* the action username. Otherwise, it is sent to all connected RMI clients.
|
|
||||||
*
|
|
||||||
* @param action the network action to send.
|
|
||||||
* @throws RemoteException if an RMI communication error occurs.
|
|
||||||
*/
|
|
||||||
public void notifyAll(NetworkEvent action) throws RemoteException {
|
|
||||||
for (Map.Entry<String,IClientCallback> entry : clients.entrySet()) {
|
|
||||||
if(!action.getIsError() ||(action.getIsError()&& action.getUsername().equals(entry.getKey())))
|
|
||||||
entry.getValue().onAction(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a game model to all RMI clients.
|
|
||||||
*
|
|
||||||
* @param model the game model to send to all connected RMI clients.
|
|
||||||
* @throws RemoteException if an RMI communication error occurs.
|
|
||||||
*/
|
|
||||||
public void notifyAll(Game model) throws RemoteException {
|
|
||||||
for (IClientCallback cb : clients.values()) {
|
|
||||||
cb.onGameInit(model);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -249,27 +267,22 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
|
|||||||
*/
|
*/
|
||||||
public boolean start() {
|
public boolean start() {
|
||||||
try {
|
try {
|
||||||
System.setProperty("java.rmi.server.hostname", host); // o il tuo IP/hostname
|
System.setProperty("java.rmi.server.hostname", host);
|
||||||
registry = LocateRegistry.createRegistry(nPort);
|
registry = LocateRegistry.createRegistry(nPort);
|
||||||
registry.rebind("RMIGameServer", this);
|
registry.rebind("RMIGameServer", this);
|
||||||
System.out.println("RMI Server started on port: "+nPort);
|
System.out.println("RMI Server started on port: " + nPort);
|
||||||
return true;
|
return true;
|
||||||
}
|
} catch (Exception e) {
|
||||||
catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the RMI server.
|
|
||||||
* @return true if the server stops successfully, false otherwise
|
|
||||||
*/
|
|
||||||
public boolean stop() {
|
public boolean stop() {
|
||||||
try {
|
try {
|
||||||
registry.unbind("RMIGameServer");
|
registry.unbind("RMIGameServer");
|
||||||
UnicastRemoteObject.unexportObject(this, true);
|
UnicastRemoteObject.unexportObject(this, true);
|
||||||
|
watchdogs.values().forEach(wd -> { /* watchdogs shut themselves down */ });
|
||||||
System.out.println("RMI Server fermato");
|
System.out.println("RMI Server fermato");
|
||||||
return true;
|
return true;
|
||||||
} catch (RemoteException | NotBoundException e) {
|
} catch (RemoteException | NotBoundException e) {
|
||||||
@@ -279,4 +292,19 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and starts a {@link RMIHeartbeat} for {@code username}.
|
||||||
|
* Also seeds the watchdog with the current model if one already exists
|
||||||
|
* (reconnection case).
|
||||||
|
*/
|
||||||
|
private void startWatchdog(String username) {
|
||||||
|
RMIHeartbeat wd = new RMIHeartbeat(
|
||||||
|
username, playerList, clients, actionQueue);
|
||||||
|
if (model != null) wd.setGame(model);
|
||||||
|
watchdogs.put(username, wd);
|
||||||
|
wd.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user