From e78096d2e0308931945b148611d67f38fa1a4404 Mon Sep 17 00:00:00 2001 From: rubenpirreram Date: Thu, 7 May 2026 21:17:00 +0200 Subject: [PATCH] Partial implementation client resilience --- .../ingsw/gc14/Controller/GameController.java | 5 ++- .../java/it/polimi/ingsw/gc14/LimitedMap.java | 8 ++++- .../polimi/ingsw/gc14/Network/EventType.java | 4 +-- .../Network/NetworkEvents/SkipNoDrawable.java | 2 +- .../NetworkEvents/SkipPlayerDisconnected.java | 33 +++++++++++++++++++ .../gc14/Network/TCP/Client/TCPClient.java | 19 +++++++---- .../Network/TCP/Server/ClientHandler.java | 18 +++++++--- .../Network/TCP/Server/HeartbeatHandler.java | 3 +- .../gc14/Network/TCP/Server/TCPServer.java | 24 ++++++++------ .../it/polimi/ingsw/gc14/ServerLauncher.java | 19 +++++++---- 10 files changed, 101 insertions(+), 34 deletions(-) create mode 100644 src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SkipPlayerDisconnected.java diff --git a/src/main/java/it/polimi/ingsw/gc14/Controller/GameController.java b/src/main/java/it/polimi/ingsw/gc14/Controller/GameController.java index 8b2e1be..16c9ec4 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Controller/GameController.java +++ b/src/main/java/it/polimi/ingsw/gc14/Controller/GameController.java @@ -31,8 +31,11 @@ public class GameController { public GameController() { } //TODO - public boolean SkipNotConnectedPlayer(Player player) + public boolean SkipNotConnectedPlayer(String username) { + Player player= model.getPlayerByUsername(username); + if(player==null) + return false; return model.SkipNotConnectedPlayer(player); } /** diff --git a/src/main/java/it/polimi/ingsw/gc14/LimitedMap.java b/src/main/java/it/polimi/ingsw/gc14/LimitedMap.java index e209803..d4833ae 100644 --- a/src/main/java/it/polimi/ingsw/gc14/LimitedMap.java +++ b/src/main/java/it/polimi/ingsw/gc14/LimitedMap.java @@ -48,8 +48,14 @@ public class LimitedMap implements Map { */ @Override public synchronized V put(K key, V value) { + boolean added = true; + if(map.size()==limit) { + if(!map.containsKey(key)) + return null; + added = false; + } V result = map.put(key, value); - if (map.size() >= limit) { + if (map.size() >= limit && added) { action.run(); } return result; diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/EventType.java b/src/main/java/it/polimi/ingsw/gc14/Network/EventType.java index 46a6d15..dd887b5 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/EventType.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/EventType.java @@ -13,7 +13,7 @@ public enum EventType { DRAW_LOWER_BUILD, PICK_OPTIONAL_TRIBE, PICK_OPTIONAL_BUILD, - SKIP_UPPER, - SKIP_LOWER, + SKIP_NO_DRAWABLE, + SKIP_PLAYER_DISCONNECTED, NO_OPTIONAL_CARD } diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SkipNoDrawable.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SkipNoDrawable.java index 7651ba1..3f84ec8 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SkipNoDrawable.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SkipNoDrawable.java @@ -17,7 +17,7 @@ public class SkipNoDrawable extends NetworkEvent implements Serializable{ * @param username the name of the player requesting the event */ public SkipNoDrawable(String username){ - super(username, EventType.SKIP_LOWER, false); + super(username, EventType.SKIP_PLAYER_DISCONNECTED, false); } /** diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SkipPlayerDisconnected.java b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SkipPlayerDisconnected.java new file mode 100644 index 0000000..63ea7fd --- /dev/null +++ b/src/main/java/it/polimi/ingsw/gc14/Network/NetworkEvents/SkipPlayerDisconnected.java @@ -0,0 +1,33 @@ +package it.polimi.ingsw.gc14.Network.NetworkEvents; + +import it.polimi.ingsw.gc14.Controller.GameController; +import it.polimi.ingsw.gc14.Network.EventType; +import it.polimi.ingsw.gc14.Network.NetworkEvent; + +import java.io.Serializable; + +/** + * NetworkEvent to avoid drawing a card from the lower card list + */ +public class SkipPlayerDisconnected extends NetworkEvent implements Serializable{ + + /** + * Class constructor. + * Initializes all the attributes. + * @param username the name of the player requesting the event + */ + public SkipPlayerDisconnected(String username){ + super(username, EventType.SKIP_PLAYER_DISCONNECTED, false); + } + + /** + * @param gameController the Game Controller on which to apply the event + * @return true if the player could skipTheTurn, false otherwise + */ + @Override + public boolean apply(GameController gameController){ + return gameController.SkipNotConnectedPlayer(username); + } + +} + diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Client/TCPClient.java b/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Client/TCPClient.java index 543c7ac..2986e75 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Client/TCPClient.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Client/TCPClient.java @@ -72,13 +72,15 @@ public class TCPClient implements IClient { communicationSocket = new Socket(hostname, mainPort); socketSend = new ObjectOutputStream(communicationSocket.getOutputStream()); socketReceive = new ObjectInputStream(communicationSocket.getInputStream()); - doEvent(new AddPlayer(user, proposedNPlayers)); - - if (communicationSocket.getInputStream().read() == -1) { + NetworkEvent event= new AddPlayer(user, proposedNPlayers); + System.out.println("Sending event: " + event); + socketSend.writeObject(event); + int read= communicationSocket.getInputStream().read(); + if ( read== -1) { System.out.println("Could not connect to server"); return false; } - + new Thread(this::receiveMessage, "tcp-reader").start(); // Socket heartbeat this.heartbeatSocket = new Socket(hostname, heartbeatPort); this.heartbeatOut =heartbeatSocket.getOutputStream() ; @@ -89,7 +91,7 @@ public class TCPClient implements IClient { heartbeatOut.flush(); running = true; - new Thread(this::receiveMessage, "tcp-reader").start(); + new Thread(this::heartbeatLoop, "heartbeat").start(); return true; @@ -157,7 +159,6 @@ public class TCPClient implements IClient { e.printStackTrace(); break; } - if (read instanceof NetworkEvent event) { //TODO: avoid instanceof if (event.getIsError()) { controller.view.showError(event.toString()); @@ -165,6 +166,7 @@ public class TCPClient implements IClient { event.apply(controller.localController); controller.view.render(); } + } else if (read instanceof Game model) { controller.setModel(model); controller.view.render(); @@ -279,7 +281,10 @@ public class TCPClient implements IClient { */ private void doEvent(NetworkEvent event) { try { - socketSend.writeObject(event); + synchronized (socketSend) { + System.out.println("Sending event: " + event); + socketSend.writeObject(event); + } } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/ClientHandler.java b/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/ClientHandler.java index 2e9e8d2..1071fee 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/ClientHandler.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/ClientHandler.java @@ -3,6 +3,7 @@ package it.polimi.ingsw.gc14.Network.TCP.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.io.*; import java.net.*; @@ -23,6 +24,7 @@ public class ClientHandler implements Runnable { private boolean running ; + private Game game; /** * Returns the username associated with this client. * @@ -70,6 +72,7 @@ public class ClientHandler implements Runnable { this.out = out; this.clientHandlers = clientHandlers; this.actionQueue = actionQueue; + this.limitedMap = playersMap; } @@ -82,9 +85,11 @@ public class ClientHandler implements Runnable { try { running = true; while (running) { - NetworkEvent event = (NetworkEvent) in.readObject(); - if (!actionQueue.add(event)) { - System.out.println("Error inserting action into queue"); + synchronized (out){ + NetworkEvent event = (NetworkEvent) in.readObject(); + if (!actionQueue.add(event)) { + System.out.println("Error inserting action into queue"); + } } } } catch (IOException e) { @@ -116,6 +121,7 @@ public class ClientHandler implements Runnable { * @param game The current state of the game to send to the client. */ public synchronized void notifyModel(Game game) { + this.game = game; try { out.writeObject(game); } catch (IOException e) { @@ -124,9 +130,13 @@ public class ClientHandler implements Runnable { } public void disconnect() { - if (!running) return; running = false; clientHandlers.remove(this); + limitedMap.put(username, false); + if(this.game!=null && this.game.getCurrentState().getCurrentPlayer().getUserName().equals(username)) { + actionQueue.add(new SkipPlayerDisconnected(username)); + } + System.out.println("Disconnected player: " + username); try { clientSocket.close(); } catch (IOException ignored) {} } } \ No newline at end of file diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/HeartbeatHandler.java b/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/HeartbeatHandler.java index 8353369..a0dde68 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/HeartbeatHandler.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/HeartbeatHandler.java @@ -4,6 +4,7 @@ import java.io.*; import java.net.*; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; public class HeartbeatHandler implements Runnable { @@ -66,10 +67,10 @@ public class HeartbeatHandler implements Runnable { private void disconnect() { - if (!running) return; running = false; watchdog.shutdownNow(); mainHandler.disconnect(); // disconnette anche il socket principale + System.out.println("Disconnected: " + username); try { socket.close(); } catch (IOException ignored) {} } } diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/TCPServer.java b/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/TCPServer.java index dffc508..e30c8a2 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/TCPServer.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/TCPServer.java @@ -71,7 +71,6 @@ public class TCPServer { 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(); @@ -88,7 +87,7 @@ public class TCPServer { continue; } - synchronized (controller) { + if (playerList.isEmpty()) { Game model = new Game(eventAddPlayer.getProposedNPlayer()); controller.setModel(model); @@ -100,35 +99,40 @@ public class TCPServer { if (controller.addPlayer(username)) { // nuovo giocatore playerList.put(username, true); - clientSocket.getOutputStream().write(1); 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 - pendingHeartbeat.put(username, handler); } else if (playerList.containsKey(username) && !playerList.get(username)) { // riconnessione playerList.put(username, true); - clientSocket.getOutputStream().write(1); System.out.println("Reconnected player: " + username); ClientHandler handler = new ClientHandler( username, clientSocket, clientSend, clientReceive, clientHandlers, playerList, actionQueue ); - handler.notifyModel(controller.getModel()); + clientSocket.getOutputStream().write(1); + pendingHeartbeat.put(username, handler); + synchronized (controller) { + handler.notifyModel(controller.getModel()); + } + Thread thread = new Thread(handler); + thread.start(); clientHandlers.add(handler); connectedPlayers++; - pendingHeartbeat.put(username, handler); + } else { @@ -136,7 +140,7 @@ public class TCPServer { clientSocket.close(); System.out.println("Player could not be added. Connection terminated."); } - } + } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); diff --git a/src/main/java/it/polimi/ingsw/gc14/ServerLauncher.java b/src/main/java/it/polimi/ingsw/gc14/ServerLauncher.java index 4db758c..f63254f 100644 --- a/src/main/java/it/polimi/ingsw/gc14/ServerLauncher.java +++ b/src/main/java/it/polimi/ingsw/gc14/ServerLauncher.java @@ -3,8 +3,10 @@ package it.polimi.ingsw.gc14; import it.polimi.ingsw.gc14.Controller.GameController; import it.polimi.ingsw.gc14.Model.Game; +import it.polimi.ingsw.gc14.Model.Player; import it.polimi.ingsw.gc14.Network.ClientPlayer; import it.polimi.ingsw.gc14.Network.NetworkEvent; +import it.polimi.ingsw.gc14.Network.NetworkEvents.SkipPlayerDisconnected; import it.polimi.ingsw.gc14.Network.RMI.Server.RMIServer; import it.polimi.ingsw.gc14.Network.TCP.Server.TCPServer; import it.polimi.ingsw.gc14.View.TUI.TUI; @@ -85,13 +87,16 @@ public class ServerLauncher { * @throws RemoteException if an RMI error occurs */ public boolean doFirstEvent() throws InterruptedException, RemoteException { - NetworkEvent event = actionQueue.take(); - event.setIsError(!event.apply(gameController)); - - serverRMI.notifyAll(event); - serverTCP.notifyAll(event); - - return !event.getIsError(); + synchronized (gameController) { + NetworkEvent event = actionQueue.take(); + event.setIsError(!event.apply(gameController)); + serverRMI.notifyAll(event); + serverTCP.notifyAll(event); + if (!playerList.get(gameController.getModel().getCurrentState().getCurrentPlayer().getUserName())) { + actionQueue.offer(new SkipPlayerDisconnected(gameController.getModel().getCurrentState().getCurrentPlayer().getUserName())); + } + return !event.getIsError(); + } }