Partial implementation client resilience

This commit is contained in:
rubenpirreram
2026-05-07 21:17:00 +02:00
parent 7757b38155
commit e78096d2e0
10 changed files with 101 additions and 34 deletions
@@ -31,8 +31,11 @@ public class GameController {
public GameController() { public GameController() {
} }
//TODO //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); return model.SkipNotConnectedPlayer(player);
} }
/** /**
@@ -48,8 +48,14 @@ public class LimitedMap<K, V> implements Map<K, V> {
*/ */
@Override @Override
public synchronized V put(K key, V value) { 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); V result = map.put(key, value);
if (map.size() >= limit) { if (map.size() >= limit && added) {
action.run(); action.run();
} }
return result; return result;
@@ -13,7 +13,7 @@ public enum EventType {
DRAW_LOWER_BUILD, DRAW_LOWER_BUILD,
PICK_OPTIONAL_TRIBE, PICK_OPTIONAL_TRIBE,
PICK_OPTIONAL_BUILD, PICK_OPTIONAL_BUILD,
SKIP_UPPER, SKIP_NO_DRAWABLE,
SKIP_LOWER, SKIP_PLAYER_DISCONNECTED,
NO_OPTIONAL_CARD NO_OPTIONAL_CARD
} }
@@ -17,7 +17,7 @@ public class SkipNoDrawable extends NetworkEvent implements Serializable{
* @param username the name of the player requesting the event * @param username the name of the player requesting the event
*/ */
public SkipNoDrawable(String username){ public SkipNoDrawable(String username){
super(username, EventType.SKIP_LOWER, false); super(username, EventType.SKIP_PLAYER_DISCONNECTED, false);
} }
/** /**
@@ -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);
}
}
@@ -72,13 +72,15 @@ public class TCPClient implements IClient {
communicationSocket = new Socket(hostname, mainPort); communicationSocket = new Socket(hostname, mainPort);
socketSend = new ObjectOutputStream(communicationSocket.getOutputStream()); socketSend = new ObjectOutputStream(communicationSocket.getOutputStream());
socketReceive = new ObjectInputStream(communicationSocket.getInputStream()); socketReceive = new ObjectInputStream(communicationSocket.getInputStream());
doEvent(new AddPlayer(user, proposedNPlayers)); NetworkEvent event= new AddPlayer(user, proposedNPlayers);
System.out.println("Sending event: " + event);
if (communicationSocket.getInputStream().read() == -1) { socketSend.writeObject(event);
int read= communicationSocket.getInputStream().read();
if ( read== -1) {
System.out.println("Could not connect to server"); System.out.println("Could not connect to server");
return false; return false;
} }
new Thread(this::receiveMessage, "tcp-reader").start();
// Socket heartbeat // Socket heartbeat
this.heartbeatSocket = new Socket(hostname, heartbeatPort); this.heartbeatSocket = new Socket(hostname, heartbeatPort);
this.heartbeatOut =heartbeatSocket.getOutputStream() ; this.heartbeatOut =heartbeatSocket.getOutputStream() ;
@@ -89,7 +91,7 @@ public class TCPClient implements IClient {
heartbeatOut.flush(); heartbeatOut.flush();
running = true; running = true;
new Thread(this::receiveMessage, "tcp-reader").start();
new Thread(this::heartbeatLoop, "heartbeat").start(); new Thread(this::heartbeatLoop, "heartbeat").start();
return true; return true;
@@ -157,7 +159,6 @@ public class TCPClient implements IClient {
e.printStackTrace(); e.printStackTrace();
break; break;
} }
if (read instanceof NetworkEvent event) { //TODO: avoid instanceof if (read instanceof NetworkEvent event) { //TODO: avoid instanceof
if (event.getIsError()) { if (event.getIsError()) {
controller.view.showError(event.toString()); controller.view.showError(event.toString());
@@ -165,6 +166,7 @@ public class TCPClient implements IClient {
event.apply(controller.localController); event.apply(controller.localController);
controller.view.render(); controller.view.render();
} }
} else if (read instanceof Game model) { } else if (read instanceof Game model) {
controller.setModel(model); controller.setModel(model);
controller.view.render(); controller.view.render();
@@ -279,7 +281,10 @@ public class TCPClient implements IClient {
*/ */
private void doEvent(NetworkEvent event) { private void doEvent(NetworkEvent event) {
try { try {
synchronized (socketSend) {
System.out.println("Sending event: " + event);
socketSend.writeObject(event); socketSend.writeObject(event);
}
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -3,6 +3,7 @@ package it.polimi.ingsw.gc14.Network.TCP.Server;
import it.polimi.ingsw.gc14.LimitedMap; import it.polimi.ingsw.gc14.LimitedMap;
import it.polimi.ingsw.gc14.Model.Game; import it.polimi.ingsw.gc14.Model.Game;
import it.polimi.ingsw.gc14.Network.NetworkEvent; import it.polimi.ingsw.gc14.Network.NetworkEvent;
import it.polimi.ingsw.gc14.Network.NetworkEvents.SkipPlayerDisconnected;
import java.io.*; import java.io.*;
import java.net.*; import java.net.*;
@@ -23,6 +24,7 @@ public class ClientHandler implements Runnable {
private boolean running ; private boolean running ;
private Game game;
/** /**
* Returns the username associated with this client. * Returns the username associated with this client.
* *
@@ -70,6 +72,7 @@ public class ClientHandler implements Runnable {
this.out = out; this.out = out;
this.clientHandlers = clientHandlers; this.clientHandlers = clientHandlers;
this.actionQueue = actionQueue; this.actionQueue = actionQueue;
this.limitedMap = playersMap;
} }
@@ -82,11 +85,13 @@ public class ClientHandler implements Runnable {
try { try {
running = true; running = true;
while (running) { while (running) {
synchronized (out){
NetworkEvent event = (NetworkEvent) in.readObject(); NetworkEvent event = (NetworkEvent) in.readObject();
if (!actionQueue.add(event)) { if (!actionQueue.add(event)) {
System.out.println("Error inserting action into queue"); System.out.println("Error inserting action into queue");
} }
} }
}
} catch (IOException e) { } catch (IOException e) {
clientHandlers.remove(this); clientHandlers.remove(this);
e.printStackTrace(); e.printStackTrace();
@@ -116,6 +121,7 @@ public class ClientHandler implements Runnable {
* @param game The current state of the game to send to the client. * @param game The current state of the game to send to the client.
*/ */
public synchronized void notifyModel(Game game) { public synchronized void notifyModel(Game game) {
this.game = game;
try { try {
out.writeObject(game); out.writeObject(game);
} catch (IOException e) { } catch (IOException e) {
@@ -124,9 +130,13 @@ public class ClientHandler implements Runnable {
} }
public void disconnect() { public void disconnect() {
if (!running) return;
running = false; running = false;
clientHandlers.remove(this); 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) {} try { clientSocket.close(); } catch (IOException ignored) {}
} }
} }
@@ -4,6 +4,7 @@ import java.io.*;
import java.net.*; import java.net.*;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class HeartbeatHandler implements Runnable { public class HeartbeatHandler implements Runnable {
@@ -66,10 +67,10 @@ public class HeartbeatHandler implements Runnable {
private void disconnect() { private void disconnect() {
if (!running) return;
running = false; running = false;
watchdog.shutdownNow(); watchdog.shutdownNow();
mainHandler.disconnect(); // disconnette anche il socket principale mainHandler.disconnect(); // disconnette anche il socket principale
System.out.println("Disconnected: " + username);
try { socket.close(); } catch (IOException ignored) {} try { socket.close(); } catch (IOException ignored) {}
} }
} }
@@ -71,7 +71,6 @@ public class TCPServer {
ObjectOutputStream clientSend = new ObjectOutputStream(clientSocket.getOutputStream()); ObjectOutputStream clientSend = new ObjectOutputStream(clientSocket.getOutputStream());
ObjectInputStream clientReceive = new ObjectInputStream(clientSocket.getInputStream()); ObjectInputStream clientReceive = new ObjectInputStream(clientSocket.getInputStream());
NetworkEvent event = (NetworkEvent) clientReceive.readObject(); NetworkEvent event = (NetworkEvent) clientReceive.readObject();
if (!(event.getEventType() == EventType.ADD_PLAYER)) { if (!(event.getEventType() == EventType.ADD_PLAYER)) {
clientSocket.getOutputStream().write(-1); clientSocket.getOutputStream().write(-1);
clientSocket.close(); clientSocket.close();
@@ -88,7 +87,7 @@ public class TCPServer {
continue; continue;
} }
synchronized (controller) {
if (playerList.isEmpty()) { if (playerList.isEmpty()) {
Game model = new Game(eventAddPlayer.getProposedNPlayer()); Game model = new Game(eventAddPlayer.getProposedNPlayer());
controller.setModel(model); controller.setModel(model);
@@ -100,35 +99,40 @@ public class TCPServer {
if (controller.addPlayer(username)) { if (controller.addPlayer(username)) {
// nuovo giocatore // nuovo giocatore
playerList.put(username, true); playerList.put(username, true);
clientSocket.getOutputStream().write(1);
System.out.println("Accepted player: " + username); System.out.println("Accepted player: " + username);
ClientHandler handler = new ClientHandler( ClientHandler handler = new ClientHandler(
username, clientSocket, clientSend, clientReceive, username, clientSocket, clientSend, clientReceive,
clientHandlers,playerList, actionQueue clientHandlers,playerList, actionQueue
); );
clientSocket.getOutputStream().write(1);
pendingHeartbeat.put(username, handler);
Thread thread = new Thread(handler);
thread.start();
clientHandlers.add(handler); clientHandlers.add(handler);
connectedPlayers++; connectedPlayers++;
// metti in attesa del socket heartbeat // metti in attesa del socket heartbeat
pendingHeartbeat.put(username, handler);
} else if (playerList.containsKey(username) && !playerList.get(username)) { } else if (playerList.containsKey(username) && !playerList.get(username)) {
// riconnessione // riconnessione
playerList.put(username, true); playerList.put(username, true);
clientSocket.getOutputStream().write(1);
System.out.println("Reconnected player: " + username); System.out.println("Reconnected player: " + username);
ClientHandler handler = new ClientHandler( ClientHandler handler = new ClientHandler(
username, clientSocket, clientSend, clientReceive, username, clientSocket, clientSend, clientReceive,
clientHandlers, playerList, actionQueue clientHandlers, playerList, actionQueue
); );
clientSocket.getOutputStream().write(1);
pendingHeartbeat.put(username, handler);
synchronized (controller) {
handler.notifyModel(controller.getModel()); handler.notifyModel(controller.getModel());
}
Thread thread = new Thread(handler);
thread.start();
clientHandlers.add(handler); clientHandlers.add(handler);
connectedPlayers++; connectedPlayers++;
pendingHeartbeat.put(username, handler);
} else { } else {
@@ -136,7 +140,7 @@ public class TCPServer {
clientSocket.close(); clientSocket.close();
System.out.println("Player could not be added. Connection terminated."); System.out.println("Player could not be added. Connection terminated.");
} }
}
} catch (IOException | ClassNotFoundException e) { } catch (IOException | ClassNotFoundException e) {
e.printStackTrace(); e.printStackTrace();
@@ -3,8 +3,10 @@ package it.polimi.ingsw.gc14;
import it.polimi.ingsw.gc14.Controller.GameController; import it.polimi.ingsw.gc14.Controller.GameController;
import it.polimi.ingsw.gc14.Model.Game; 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.ClientPlayer;
import it.polimi.ingsw.gc14.Network.NetworkEvent; 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.RMI.Server.RMIServer;
import it.polimi.ingsw.gc14.Network.TCP.Server.TCPServer; import it.polimi.ingsw.gc14.Network.TCP.Server.TCPServer;
import it.polimi.ingsw.gc14.View.TUI.TUI; import it.polimi.ingsw.gc14.View.TUI.TUI;
@@ -85,14 +87,17 @@ public class ServerLauncher {
* @throws RemoteException if an RMI error occurs * @throws RemoteException if an RMI error occurs
*/ */
public boolean doFirstEvent() throws InterruptedException, RemoteException { public boolean doFirstEvent() throws InterruptedException, RemoteException {
synchronized (gameController) {
NetworkEvent event = actionQueue.take(); NetworkEvent event = actionQueue.take();
event.setIsError(!event.apply(gameController)); event.setIsError(!event.apply(gameController));
serverRMI.notifyAll(event); serverRMI.notifyAll(event);
serverTCP.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(); return !event.getIsError();
} }
}
/** /**