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() {
}
//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);
}
/**
@@ -48,8 +48,14 @@ public class LimitedMap<K, V> implements Map<K, V> {
*/
@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;
@@ -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
}
@@ -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);
}
/**
@@ -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);
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 {
synchronized (socketSend) {
System.out.println("Sending event: " + event);
socketSend.writeObject(event);
}
} catch (IOException e) {
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.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,11 +85,13 @@ public class ClientHandler implements Runnable {
try {
running = true;
while (running) {
synchronized (out){
NetworkEvent event = (NetworkEvent) in.readObject();
if (!actionQueue.add(event)) {
System.out.println("Error inserting action into queue");
}
}
}
} catch (IOException e) {
clientHandlers.remove(this);
e.printStackTrace();
@@ -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) {}
}
}
@@ -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) {}
}
}
@@ -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
);
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();
@@ -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,14 +87,17 @@ public class ServerLauncher {
* @throws RemoteException if an RMI error occurs
*/
public boolean doFirstEvent() throws InterruptedException, RemoteException {
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();
}
}
/**