Add: PING PONG TCP
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 <T> the type of elements held in this list.
|
||||
* @param <K> the type of keys maintained by this map.
|
||||
* @param <V> the type of mapped values.
|
||||
*/
|
||||
public class LimitedList<T> extends ArrayList<T> {
|
||||
public class LimitedMap<K, V> implements Map<K, V> {
|
||||
|
||||
private final LinkedHashMap<K, V> 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<? extends K, ? extends V> m) { m.forEach(this::put); }
|
||||
|
||||
@Override
|
||||
public synchronized void clear() { map.clear(); }
|
||||
|
||||
@Override
|
||||
public synchronized Set<K> keySet() { return map.keySet(); }
|
||||
|
||||
@Override
|
||||
public synchronized Collection<V> values() { return map.values(); }
|
||||
|
||||
@Override
|
||||
public synchronized Set<Entry<K, V>> 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; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<String> playerList;
|
||||
private LimitedMap<String,Boolean> 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<NetworkEvent> actionQueue,LimitedList<String> playerList,String host) throws RemoteException {
|
||||
public RMIServer(GameController controller, int nPort, BlockingQueue<NetworkEvent> actionQueue, LimitedMap<String,Boolean> 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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
// 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.
|
||||
|
||||
@@ -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<ClientHandler> clientHandlers;
|
||||
|
||||
//TODO
|
||||
LimitedMap<String,Boolean> limitedMap;
|
||||
|
||||
/** Queue containing the events to be applied to the game model */
|
||||
BlockingQueue<NetworkEvent> 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<ClientHandler> clientHandlers, BlockingQueue<NetworkEvent> actionQueue) {
|
||||
public ClientHandler(String username, Socket clientSocket, ObjectOutputStream out, ObjectInputStream in, List<ClientHandler> clientHandlers, LimitedMap<String,Boolean> playersMap, BlockingQueue<NetworkEvent> 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) {}
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
}
|
||||
@@ -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<NetworkEvent> actionQueue;
|
||||
LimitedMap<String, Boolean> playerList;
|
||||
List<ClientHandler> 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<String> playerList;
|
||||
// Mappa temporanea: username → ClientHandler
|
||||
// Serve per associare il socket heartbeat al giusto ClientHandler
|
||||
private final Map<String, ClientHandler> pendingHeartbeat = new ConcurrentHashMap<>();
|
||||
|
||||
/** List containing all client's handlers */
|
||||
private List<ClientHandler> 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<NetworkEvent> actionQueue, LimitedList<String> playerList){
|
||||
public TCPServer(GameController controller, int port, int heartbeatPort,
|
||||
BlockingQueue<NetworkEvent> actionQueue, LimitedMap<String, Boolean> 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));
|
||||
clientSocket.getOutputStream().write(-1);
|
||||
clientSocket.close();
|
||||
System.out.println("Invalid parameters. Connection terminated.\n");
|
||||
System.out.println("Invalid parameters. Connection terminated.");
|
||||
continue;
|
||||
}
|
||||
|
||||
synchronized (controller) {
|
||||
if (playerList.isEmpty()){
|
||||
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++;
|
||||
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);
|
||||
|
||||
|
||||
Thread t = new Thread(clientHandler);
|
||||
t.start();
|
||||
} else {
|
||||
clientSocket.getOutputStream().write((int) (-1));
|
||||
clientSocket.getOutputStream().write(-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));
|
||||
}
|
||||
}
|
||||
@@ -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<String> playerList;
|
||||
static LimitedMap<String,Boolean> 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<String,Boolean>(5, ()->{});
|
||||
BlockingQueue<NetworkEvent> 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(()->{
|
||||
|
||||
Reference in New Issue
Block a user