From 7757b38155898cd6d0875d040309a410d9af9f05 Mon Sep 17 00:00:00 2001 From: rubenpirreram Date: Thu, 7 May 2026 18:09:39 +0200 Subject: [PATCH] Add: PING PONG TCP --- .../polimi/ingsw/gc14/ClientLauncherGUI.java | 2 +- .../polimi/ingsw/gc14/ClientLauncherTUI.java | 2 +- .../java/it/polimi/ingsw/gc14/LimitedMap.java | 94 +++++--- .../ingsw/gc14/Network/ClientPlayer.java | 15 ++ .../gc14/Network/RMI/Server/RMIServer.java | 10 +- .../gc14/Network/TCP/Client/TCPClient.java | 87 ++++++- .../Network/TCP/Server/ClientHandler.java | 20 +- .../Network/TCP/Server/HeartbeatHandler.java | 75 ++++++ .../gc14/Network/TCP/Server/TCPServer.java | 227 ++++++++++-------- .../it/polimi/ingsw/gc14/ServerLauncher.java | 16 +- 10 files changed, 388 insertions(+), 160 deletions(-) create mode 100644 src/main/java/it/polimi/ingsw/gc14/Network/ClientPlayer.java create mode 100644 src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/HeartbeatHandler.java diff --git a/src/main/java/it/polimi/ingsw/gc14/ClientLauncherGUI.java b/src/main/java/it/polimi/ingsw/gc14/ClientLauncherGUI.java index dfec71d..b42f6ec 100644 --- a/src/main/java/it/polimi/ingsw/gc14/ClientLauncherGUI.java +++ b/src/main/java/it/polimi/ingsw/gc14/ClientLauncherGUI.java @@ -42,7 +42,7 @@ public class ClientLauncherGUI extends Application { Platform.runLater(() -> loginView.setErrore("Connessione RMI fallita.")); } } else { - TCPClient client = new TCPClient(controller, ip, 8080); + TCPClient client = new TCPClient(controller, ip, 8080,8081); if (client.connect(nome, numPlayer)) { controller.setClient(client); } else { diff --git a/src/main/java/it/polimi/ingsw/gc14/ClientLauncherTUI.java b/src/main/java/it/polimi/ingsw/gc14/ClientLauncherTUI.java index aa7bb88..45a1da6 100644 --- a/src/main/java/it/polimi/ingsw/gc14/ClientLauncherTUI.java +++ b/src/main/java/it/polimi/ingsw/gc14/ClientLauncherTUI.java @@ -88,7 +88,7 @@ public class ClientLauncherTUI { // TCP } else if (networkType == 1) { // Connect - TCPClient client = new TCPClient(controller, IP, 8080); + TCPClient client = new TCPClient(controller, IP, 8080,8081); if (client.connect(username, proposedNumPlayers)) { System.out.println("Succesfully connected to TCP server\n\n"); } else { diff --git a/src/main/java/it/polimi/ingsw/gc14/LimitedMap.java b/src/main/java/it/polimi/ingsw/gc14/LimitedMap.java index 5ac39ff..e209803 100644 --- a/src/main/java/it/polimi/ingsw/gc14/LimitedMap.java +++ b/src/main/java/it/polimi/ingsw/gc14/LimitedMap.java @@ -1,75 +1,111 @@ package it.polimi.ingsw.gc14; -import java.util.ArrayList; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; /** - * An {@link ArrayList} with a configurable size limit and an associated action. + * A {@link LinkedHashMap} with a configurable size limit and an associated action. * When the number of elements reaches or exceeds the limit, the specified action is automatically triggered. + * This implementation is thread-safe. * - * @param the type of elements held in this list. + * @param the type of keys maintained by this map. + * @param the type of mapped values. */ -public class LimitedList extends ArrayList { +public class LimitedMap implements Map { + + private final LinkedHashMap map = new LinkedHashMap<>(); /** - * The maximum number of elements allowed in the list before the action is triggered. + * The maximum number of elements allowed in the map before the action is triggered. */ - private int limit; + private volatile int limit; /** - * The action to execute when the list size reaches or exceeds the limit. + * The action to execute when the map size reaches or exceeds the limit. */ - private Runnable action; + private volatile Runnable action; /** - * Creates a new {@code LimitedList} with the specified limit and action. + * Creates a new {@code LimitedMap} with the specified limit and action. * * @param limit the maximum number of elements before the action is triggered. * @param action the action to execute when the limit is reached. */ - public LimitedList(int limit, Runnable action) { + public LimitedMap(int limit, Runnable action) { this.limit = limit; this.action = action; } /** - * Adds the specified element to the list. - * If the list size reaches or exceeds the limit after the insertion, the configured action is triggered. + * Associates the specified value with the specified key in this map. + * If the map size reaches or exceeds the limit after the insertion, the configured action is triggered. * - * @param element the element to add. - * @return {@code true} if the element was successfully added. + * @param key the key with which the specified value is to be associated. + * @param value the value to be associated with the specified key. + * @return the previous value associated with the key, or {@code null} if there was no mapping. */ @Override - public boolean add(T element) { - boolean result = super.add(element); - if (size() >= limit) { + public synchronized V put(K key, V value) { + V result = map.put(key, value); + if (map.size() >= limit) { action.run(); } return result; } + @Override + public synchronized V remove(Object key) { return map.remove(key); } + + @Override + public synchronized V get(Object key) { return map.get(key); } + + @Override + public synchronized boolean containsKey(Object key) { return map.containsKey(key); } + + @Override + public synchronized boolean containsValue(Object value) { return map.containsValue(value); } + + @Override + public synchronized int size() { return map.size(); } + + @Override + public synchronized boolean isEmpty() { return map.isEmpty(); } + + @Override + public synchronized void putAll(Map m) { m.forEach(this::put); } + + @Override + public synchronized void clear() { map.clear(); } + + @Override + public synchronized Set keySet() { return map.keySet(); } + + @Override + public synchronized Collection values() { return map.values(); } + + @Override + public synchronized Set> entrySet() { return map.entrySet(); } + /** - * Sets a new size limit for this list. + * Sets a new size limit for this map. * * @param num the new limit. */ - public void setLimit(int num) { - this.limit = num; - } + public void setLimit(int num) { this.limit = num; } /** - * Returns the current size limit of this list. + * Returns the current size limit of this map. * * @return the current limit. */ - public int getLimit() { - return limit; - } + public int getLimit() { return limit; } /** - * Sets a new action to execute when the list size reaches or exceeds the limit. + * Sets a new action to execute when the map size reaches or exceeds the limit. * * @param action the new action to set. */ - public void setAction(Runnable action) { - this.action = action; - } + public void setAction(Runnable action) { this.action = action; } } \ No newline at end of file diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/ClientPlayer.java b/src/main/java/it/polimi/ingsw/gc14/Network/ClientPlayer.java new file mode 100644 index 0000000..29d816d --- /dev/null +++ b/src/main/java/it/polimi/ingsw/gc14/Network/ClientPlayer.java @@ -0,0 +1,15 @@ +package it.polimi.ingsw.gc14.Network; +//TODO +public class ClientPlayer { + private String username; + + public String getUsername() { + return username; + } + public boolean connected; + + public ClientPlayer(String username,boolean connected) { + this.username = username; + this.connected = connected; + } +} diff --git a/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Server/RMIServer.java b/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Server/RMIServer.java index 66d03d2..69bdb6c 100644 --- a/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Server/RMIServer.java +++ b/src/main/java/it/polimi/ingsw/gc14/Network/RMI/Server/RMIServer.java @@ -1,7 +1,7 @@ package it.polimi.ingsw.gc14.Network.RMI.Server; import it.polimi.ingsw.gc14.Controller.GameController; -import it.polimi.ingsw.gc14.LimitedList; +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.*; @@ -44,9 +44,9 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer { /** * List containing the usernames of joined players. - * {@link LimitedList}'s limit defines at which size the list calls its action. The limit can be set using {@link LimitedList#setLimit(int)}. + * {@link LimitedMap}'s limit defines at which size the list calls its action. The limit can be set using {@link LimitedMap#setLimit(int)}. */ - private LimitedList playerList; + private LimitedMap playerList; /** @@ -59,7 +59,7 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer { * @param host the host address of the RMI server. * @throws RemoteException if an RMI error occurs. */ - public RMIServer(GameController controller, int nPort, BlockingQueue actionQueue,LimitedList playerList,String host) throws RemoteException { + public RMIServer(GameController controller, int nPort, BlockingQueue actionQueue, LimitedMap playerList, String host) throws RemoteException { this.controller = controller; this.nPort = nPort; this.actionQueue = actionQueue; @@ -89,7 +89,7 @@ public class RMIServer extends UnicastRemoteObject implements IGameServer { } if (controller.addPlayer(username)) { clients.put(username, callback); - playerList.add(username); + playerList.put(username,true); System.out.println("Accepted player: " + username); return true; } 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 f0453cc..543c7ac 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 @@ -8,11 +8,16 @@ import it.polimi.ingsw.gc14.Network.NetworkEvents.*; import java.io.*; import java.net.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; /** * Client TCP. Sends and receives messages with the TCP server. */ public class TCPClient implements IClient { + private static final int PING = 1; + private static final int PONG = 2; /** Socket TCP */ Socket communicationSocket; @@ -29,8 +34,14 @@ public class TCPClient implements IClient { /** IP address of the server to connect to */ String hostname; + private boolean running; /** TCP port */ - int port; + int mainPort; + + int heartbeatPort; + private Socket heartbeatSocket; + private OutputStream heartbeatOut; + private InputStream heartbeatIn; /** @@ -39,10 +50,11 @@ public class TCPClient implements IClient { * @param hostname The IP address of the server * @param port The TCP port of the server */ - public TCPClient(ClientController controller, String hostname, int port) { + public TCPClient(ClientController controller, String hostname, int mainPort,int heartbeatPort ) { this.controller = controller; this.hostname = hostname; - this.port = port; + this.mainPort = mainPort; + this.heartbeatPort = heartbeatPort; } @@ -56,27 +68,78 @@ public class TCPClient implements IClient { */ public boolean connect(String user, int proposedNPlayers) { try { - - communicationSocket = new Socket(hostname, port); - socketSend = new ObjectOutputStream(communicationSocket.getOutputStream()); + // Socket principale + 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) { System.out.println("Could not connect to server"); return false; - } else { - Thread listener = new Thread(() -> receiveMessage()); - listener.start(); - return true; } + + // Socket heartbeat + this.heartbeatSocket = new Socket(hostname, heartbeatPort); + this.heartbeatOut =heartbeatSocket.getOutputStream() ; + this.heartbeatIn = heartbeatSocket.getInputStream(); + + // manda subito username per associare i due socket lato server + new ObjectOutputStream(heartbeatSocket.getOutputStream()).writeObject(user); + heartbeatOut.flush(); + + running = true; + new Thread(this::receiveMessage, "tcp-reader").start(); + new Thread(this::heartbeatLoop, "heartbeat").start(); + return true; + } catch (IOException e) { e.printStackTrace(); return false; } } + private void heartbeatLoop() { + // thread che manda ping ogni 3s + ScheduledExecutorService sender = Executors.newSingleThreadScheduledExecutor(); + sender.scheduleAtFixedRate(() -> { + try { + heartbeatOut.write(PING); + heartbeatOut.flush(); + } catch (IOException e) { + sender.shutdownNow(); + disconnect(); + } + }, 0, 3, TimeUnit.SECONDS); + + // thread corrente: aspetta pong con timeout + try { + heartbeatSocket.setSoTimeout(5_000); + while (running) { + int b = heartbeatIn.read(); + if (b == -1 || b != PONG) { + disconnect(); + break; + } + // pong ricevuto → server vivo + } + } catch (SocketTimeoutException e) { + System.out.println("Server heartbeat timeout"); + disconnect(); + } catch (IOException e) { + disconnect(); + } finally { + sender.shutdownNow(); + } + } + private void disconnect() { + if (!running) return; + running = false; + try { communicationSocket.close(); } catch (IOException ignored) {} + try { heartbeatSocket.close(); } catch (IOException ignored) {} + controller.view.showError("Connessione al server persa"); + } + /** * Listens continuously for incoming objects from the server. 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 83b482c..2e9e8d2 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 @@ -1,5 +1,6 @@ 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; @@ -20,6 +21,8 @@ public class ClientHandler implements Runnable { */ private final String username; + private boolean running ; + /** * Returns the username associated with this client. * @@ -43,6 +46,9 @@ public class ClientHandler implements Runnable { */ List clientHandlers; + //TODO + LimitedMap limitedMap; + /** Queue containing the events to be applied to the game model */ BlockingQueue actionQueue; @@ -57,7 +63,7 @@ public class ClientHandler implements Runnable { * @param clientHandlers the shared list of all active client handlers. * @param actionQueue the queue containing incoming events. */ - public ClientHandler(String username, Socket clientSocket, ObjectOutputStream out, ObjectInputStream in, List clientHandlers, BlockingQueue actionQueue) { + public ClientHandler(String username, Socket clientSocket, ObjectOutputStream out, ObjectInputStream in, List clientHandlers, LimitedMap playersMap, BlockingQueue actionQueue) { this.username=username; this.clientSocket = clientSocket; this.in = in; @@ -74,8 +80,8 @@ public class ClientHandler implements Runnable { @Override public void run() { try { - - while (true) { + running = true; + while (running) { NetworkEvent event = (NetworkEvent) in.readObject(); if (!actionQueue.add(event)) { System.out.println("Error inserting action into queue"); @@ -90,6 +96,7 @@ public class ClientHandler implements Runnable { } + /** * Sends a {@link NetworkEvent} to the client. * @param event The network event to send to the client. @@ -115,4 +122,11 @@ public class ClientHandler implements Runnable { e.printStackTrace(); } } + + public void disconnect() { + if (!running) return; + running = false; + clientHandlers.remove(this); + 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 new file mode 100644 index 0000000..8353369 --- /dev/null +++ b/src/main/java/it/polimi/ingsw/gc14/Network/TCP/Server/HeartbeatHandler.java @@ -0,0 +1,75 @@ +package it.polimi.ingsw.gc14.Network.TCP.Server; + +import java.io.*; +import java.net.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class HeartbeatHandler implements Runnable { + + private static final long SILENCE_THRESHOLD_MS = 5_000; + private static final long KEEPALIVE_INTERVAL_MS = 3_000; + + private final String username; + private final Socket socket; + private final InputStream in; + private final OutputStream out; + + private static final int PING = 1; + private static final int PONG = 2; + + // riferimento al ClientHandler principale per disconnetterlo insieme + private final ClientHandler mainHandler; + + private volatile long lastReceivedTime = System.currentTimeMillis(); + private volatile boolean running = true; + + private final ScheduledExecutorService watchdog = + Executors.newSingleThreadScheduledExecutor(); + + public HeartbeatHandler(String username, Socket socket, ClientHandler mainHandler) + throws IOException { + this.username = username; + this.socket = socket; + this.mainHandler = mainHandler; + this.in = socket.getInputStream(); + this.out = socket.getOutputStream(); + } + + @Override + public void run() { + startWatchdog(); + try { + while (running) { + int b = in.read(); // blocca finché non arriva un byte + if (b == -1) { disconnect(); break; } // stream chiusa + if (b == PING) { + lastReceivedTime = System.currentTimeMillis(); + out.write(PONG); + out.flush(); + } + } + } catch (IOException e) { + disconnect(); + } + } + + private void startWatchdog() { + watchdog.scheduleAtFixedRate(() -> { + if (System.currentTimeMillis() - lastReceivedTime > SILENCE_THRESHOLD_MS) { + System.out.println("Heartbeat timeout: " + username); + disconnect(); + } + }, 1, 1, TimeUnit.SECONDS); + } + + + private void disconnect() { + if (!running) return; + running = false; + watchdog.shutdownNow(); + mainHandler.disconnect(); // disconnette anche il socket principale + 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 5380e36..dffc508 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 @@ -1,8 +1,9 @@ package it.polimi.ingsw.gc14.Network.TCP.Server; import it.polimi.ingsw.gc14.Controller.GameController; -import it.polimi.ingsw.gc14.LimitedList; +import it.polimi.ingsw.gc14.LimitedMap; import it.polimi.ingsw.gc14.Model.Game; +import it.polimi.ingsw.gc14.Network.ClientPlayer; import it.polimi.ingsw.gc14.Network.EventType; import it.polimi.ingsw.gc14.Network.NetworkEvent; import it.polimi.ingsw.gc14.Network.NetworkEvents.AddPlayer; @@ -11,151 +12,175 @@ import java.io.*; import java.net.*; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; /** * Server TCP. Accepts connections and manages all client handlers. */ public class TCPServer { - /** TCP port */ int port; + int heartbeatPort; // ← nuova porta - /** Number of currently connected clients */ - int ConnectedPlayers; - - /** Socket TCP */ + int connectedPlayers; ServerSocket socketTCP; + ServerSocket heartbeatSocketTCP; // ← nuovo ServerSocket - /** Server game's controller */ GameController controller; - - /** Queue containing the events to be applied to the game model */ BlockingQueue actionQueue; + LimitedMap playerList; + List clientHandlers; - /** - * List containing the usernames of joined players. - * {@link LimitedList}'s limit defines at which size the list calls its action. The limit can be set using {@link LimitedList#setLimit(int)}. - */ - private LimitedList playerList; + // Mappa temporanea: username → ClientHandler + // Serve per associare il socket heartbeat al giusto ClientHandler + private final Map pendingHeartbeat = new ConcurrentHashMap<>(); - /** List containing all client's handlers */ - private List clientHandlers; - - - /** - * Class constructor that initializes the attributes. - * @param controller The game controller - * @param port The TCP port - * @param actionQueue The action queue - * @param playerList The player's usernames list - */ - public TCPServer(GameController controller, int port, BlockingQueue actionQueue, LimitedList playerList){ + public TCPServer(GameController controller, int port, int heartbeatPort, + BlockingQueue actionQueue, LimitedMap playerList) { this.port = port; - this.ConnectedPlayers = 0; - this.socketTCP = null; + this.heartbeatPort = heartbeatPort; + this.connectedPlayers = 0; this.controller = controller; this.actionQueue = actionQueue; this.playerList = playerList; this.clientHandlers = new ArrayList<>(); } - - /** - * Starts the TCP server. - * If the first event is not AddPlayer, the request is rejected. - * If the desired number of player is invalid, the request is rejected. - * If this is the first player to connect, a new game model is created and passed to the controller. Additionally, the playerList's limit is set. - * If the controller successfully adds the player, the username is added to {@link #playerList} and the handler is added to {@link #clientHandlers}. - * If any error occurs, the server sends -1 back to the client. Otherwise, it sends 1. - */ - public void start(){ - - try{ + public void start() { + try { socketTCP = new ServerSocket(port); - } - catch (IOException e){ - System.out.println("Could not start the server TCP on port: " + port); + heartbeatSocketTCP = new ServerSocket(heartbeatPort); + } catch (IOException e) { + System.out.println("Could not start TCP server"); e.printStackTrace(); return; } - System.out.println("Server TCP started on port: " + port); - Socket clientSocket; - while(true){ + System.out.println("TCP server started on port: " + port); + System.out.println("Heartbeat server started on port: " + heartbeatPort); - try{ - clientSocket = socketTCP.accept(); + // 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()); NetworkEvent event = (NetworkEvent) clientReceive.readObject(); - if(!(event.getEventType() == EventType.ADD_PLAYER)){ - clientSocket.getOutputStream().write((int)(-1)); + if (!(event.getEventType() == EventType.ADD_PLAYER)) { + clientSocket.getOutputStream().write(-1); clientSocket.close(); - System.out.println("Invalid parameters. Connection terminated.\n"); + System.out.println("Invalid parameters. Connection terminated."); + continue; } - else{ - AddPlayer eventAddPlayer = (AddPlayer) event; - if (eventAddPlayer.getProposedNPlayer() < 2 || eventAddPlayer.getProposedNPlayer() > 5) { - clientSocket.getOutputStream().write((int) (-1)); + + AddPlayer eventAddPlayer = (AddPlayer) event; + + if (eventAddPlayer.getProposedNPlayer() < 2 || eventAddPlayer.getProposedNPlayer() > 5) { + clientSocket.getOutputStream().write(-1); + clientSocket.close(); + System.out.println("Invalid parameters. Connection terminated."); + continue; + } + + synchronized (controller) { + if (playerList.isEmpty()) { + Game model = new Game(eventAddPlayer.getProposedNPlayer()); + controller.setModel(model); + playerList.setLimit(eventAddPlayer.getProposedNPlayer()); + } + + String username = eventAddPlayer.getUsername(); + + 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 + ); + 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()); + clientHandlers.add(handler); + connectedPlayers++; + + pendingHeartbeat.put(username, handler); + + + } else { + clientSocket.getOutputStream().write(-1); clientSocket.close(); - System.out.println("Invalid parameters. Connection terminated.\n"); - } - synchronized (controller) { - if (playerList.isEmpty()){ - Game model = new Game(eventAddPlayer.getProposedNPlayer()); - controller.setModel(model); - playerList.setLimit(eventAddPlayer.getProposedNPlayer()); - } - if (controller.addPlayer(eventAddPlayer.getUsername())) { - playerList.add(eventAddPlayer.getUsername()); - clientSocket.getOutputStream().write((int) (1)); - - System.out.println("Accepted player: " + eventAddPlayer.getUsername()); - ClientHandler clientHandler = new ClientHandler(eventAddPlayer.getUsername(),clientSocket, clientSend, clientReceive, clientHandlers, actionQueue); - clientHandlers.add(clientHandler); - ConnectedPlayers++; - - Thread t = new Thread(clientHandler); - t.start(); - } else { - clientSocket.getOutputStream().write((int) (-1)); - clientSocket.close(); - System.out.println("Player could not be added. Connection terminated.\n"); - } + System.out.println("Player could not be added. Connection terminated."); } } - } - catch(IOException e){ + + } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } - catch(ClassNotFoundException e){ - throw new RuntimeException(e); - } } } - /** - * Sends an action to all TCP clients. - * - * @param event the network event to send to all connected TCP clients. + * Accetta connessioni sul socket heartbeat e le associa al ClientHandler giusto. + * Il client manda subito il proprio username per identificarsi. */ - public void notifyAll(NetworkEvent event){ - clientHandlers.forEach((x) -> { - if(!event.getIsError()||(event.getIsError()&& event.getUsername().equals(x.getUsername()))) - x.notifyEvent(event); + private void acceptHeartbeat() { + while (true) { + try { + Socket hbSocket = heartbeatSocketTCP.accept(); + 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); + new Thread(hb, "heartbeat-" + username).start(); + System.out.println("Heartbeat connected for: " + username); + } else { + System.out.println("No pending handler for: " + username + ", closing heartbeat."); + hbSocket.close(); + } + + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } + } + } + + public void notifyAll(NetworkEvent event) { + clientHandlers.forEach(h -> { + if (!event.getIsError() || event.getUsername().equals(h.getUsername())) + h.notifyEvent(event); }); } - - /** - * Sends a game model to all TCP clients. - * - * @param model the game model to send to all connected TCP clients. - */ - public void notifyAll(Game model){ - clientHandlers.forEach((x) -> x.notifyModel(model)); + public void notifyAll(Game model) { + clientHandlers.forEach(h -> h.notifyModel(model)); } -} +} \ No newline at end of file diff --git a/src/main/java/it/polimi/ingsw/gc14/ServerLauncher.java b/src/main/java/it/polimi/ingsw/gc14/ServerLauncher.java index 3d5bdde..4db758c 100644 --- a/src/main/java/it/polimi/ingsw/gc14/ServerLauncher.java +++ b/src/main/java/it/polimi/ingsw/gc14/ServerLauncher.java @@ -3,8 +3,8 @@ package it.polimi.ingsw.gc14; import it.polimi.ingsw.gc14.Controller.GameController; import it.polimi.ingsw.gc14.Model.Game; +import it.polimi.ingsw.gc14.Network.ClientPlayer; import it.polimi.ingsw.gc14.Network.NetworkEvent; -import it.polimi.ingsw.gc14.Network.NetworkEvents.AddPlayer; 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; @@ -21,10 +21,10 @@ import java.net.*; * The workflow is divided into two parts: game creation and game execution. * The process flow for game creation is as follows: * - The first client (TCP/RMI) requests to join the game by providing a username and the desired number of players - * - The TCP/RMI server checks {@link #playerList} and, if it is empty, sets the number of players according to the first user's request using {@link LimitedList#setLimit(int)} + * - The TCP/RMI server checks {@link #playerList} and, if it is empty, sets the number of players according to the first user's request using {@link LimitedMap#setLimit(int)} * - The TCP/RMI server creates the {@link Game} with the requested number of players and adds the player to {@link #playerList} * - Other players request to join the game (their requested number of players is ignored) - * - When the number of players in {@link #playerList} reaches the {@link LimitedList}'s limit, the list calls {@link #run()} + * - When the number of players in {@link #playerList} reaches the {@link LimitedMap}'s limit, the list calls {@link #run()} * - All players are notified of the {@link Game} * * The process flow for game execution is as follows: @@ -52,11 +52,11 @@ public class ServerLauncher { /** * List containing the usernames of joined players. - * {@link LimitedList}'s limit defines at which size the list calls its action - * Both the limit and the action can be set using {@link LimitedList#setLimit(int)} and {@link LimitedList#setAction(Runnable)} + * {@link LimitedMap}'s limit defines at which size the list calls its action + * Both the limit and the action can be set using {@link LimitedMap#setLimit(int)} and {@link LimitedMap#setAction(Runnable)} * The limit is set by the first player joining the game. The action consists in calling {@link #run()} */ - static LimitedList playerList; + static LimitedMap playerList; TUI view; @@ -105,7 +105,7 @@ public class ServerLauncher { * @throws RemoteException if this exception is issued by run method */ public static void main(String[] args) throws InterruptedException, RemoteException { - playerList = new LimitedList<>(5, ()->{}); + playerList = new LimitedMap(5, ()->{}); BlockingQueue actionQueue = new LinkedBlockingQueue<>(); GameController gameController = new GameController(); String IP; @@ -116,7 +116,7 @@ public class ServerLauncher { } RMIServer serverRMI = new RMIServer(gameController, 1099, actionQueue, playerList,IP); - TCPServer serverTCP = new TCPServer(gameController, 8080, actionQueue, playerList); + TCPServer serverTCP = new TCPServer(gameController, 8080, 8081,actionQueue, playerList); ServerLauncher launcher = new ServerLauncher(actionQueue, gameController, serverRMI, serverTCP); playerList.setAction(()->{