Merge pull request #49 from rubenpirreram/TCP

TCP
This commit is contained in:
rubenpirreram
2026-04-26 17:14:13 +02:00
committed by GitHub
4 changed files with 221 additions and 149 deletions
@@ -122,7 +122,7 @@ public class RMIServer implements IGameServer {
/**
* Sends a model game to all RMI clients.
* Sends a game model to all RMI clients.
* @param model The desired model
*/
public void notifyAll(Game model) throws RemoteException {
@@ -8,78 +8,111 @@ import it.polimi.ingsw.gc14.Network.NetworkEvents.AddPlayer;
import java.io.*;
import java.net.*;
public class TCPClient implements Serializable{
Socket communicationSocket = null;
/**
* Client TCP. Sends and receives messages with the TCP server.
*/
public class TCPClient {
/** Socket TCP */
Socket communicationSocket;
/** Input stream used receive objects from the server */
ObjectInputStream socketReceive;
/** Output stream used to send objects to the server */
ObjectOutputStream socketSend;
/** Client game's controller */
GameController controller;
/** IP address of the server to connect to */
String hostname;
/** TCP port */
int port;
public TCPClient(GameController controller, String hostname, int port){
/**
* Class constructor that initializes the attributes.
* @param controller The game controller
* @param hostname The IP address of the server
* @param port The TCP port of the server
*/
public TCPClient(GameController controller, String hostname, int port) {
this.controller = controller;
this.hostname = hostname;
this.port = port;
}
public boolean start(String user, int players){
try{
/**
* Starts the TCP connection with the server.
* Sends an {@link AddPlayer} event, if the server responds with {@code -1}, the connection is refused and the method returns {@code false}.
* Otherwise, a listener thread is started.
* @param user The username of the player
* @param proposedNPlayers The desired number of players for the game
* @return true if the connection is successful, false otherwise.
*/
public boolean start(String user, int proposedNPlayers) {
try {
communicationSocket = new Socket(hostname, port);
socketSend = new ObjectOutputStream(communicationSocket.getOutputStream());
socketReceive = new ObjectInputStream(communicationSocket.getInputStream());
socketSend.writeObject(new AddPlayer(user, players));
if(communicationSocket.getInputStream().read() == -1){
sendEvent(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());
} else {
Thread listener = new Thread(() -> receiveMessage());
listener.start();
return true;
}
}
catch(Exception e){
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
private void ReceiveMessage(){
while(true){
try{
Object read = socketReceive.readObject();
if (read instanceof NetworkEvent) { //TODO non fare con instanceof
NetworkEvent event = (NetworkEvent) read;
if(event.getIsError()) {
System.out.println(event.toString());
/**
* Listens continuously for incoming objects from the server.
* - If the received object is a {@link NetworkEvent} flagged as an error, it is printed.
* - If the received object is a valid {@link NetworkEvent}, it is applied to the game controller.
* - If the received object is a {@link Game} model, the controller's model is set.
*/
private void receiveMessage() {
while (true) {
try {
Object read = socketReceive.readObject();
if (read instanceof NetworkEvent event) { //TODO: avoid instanceof
if (event.getIsError()) {
System.out.println(event);
} else {
event.apply(controller);
//clientController.view.update(); TODO
//clientController.view.update(); TODO
}
} else if (read instanceof Game model) {
controller.setModel(model);
}
else if (read instanceof Game) {
controller.setModel((Game) read);
}
}
catch(IOException e){
} catch (IOException e) {
e.printStackTrace();
}
catch(ClassNotFoundException e){
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
return;
}
}
private void SendEvent(NetworkEvent event){
try{
/**
* Sends a {@link NetworkEvent} to the server.
* @param event The NetworkEvent to send.
*/
private void sendEvent(NetworkEvent event) {
try {
socketSend.writeObject(event);
}
catch (IOException e) {
} catch (IOException e) {
e.printStackTrace();
}
}
}
@@ -1,89 +1,105 @@
package it.polimi.ingsw.gc14.Network.TCP.Server;
import it.polimi.ingsw.gc14.Controller.GameController;
import it.polimi.ingsw.gc14.Model.Game;
import it.polimi.ingsw.gc14.Network.NetworkEvent;
import it.polimi.ingsw.gc14.Network.EventType;
import java.io.*;
import java.net.*;
import java.util.List;
import java.util.concurrent.BlockingQueue;
/**
* Handles the TCP connection with a single client.
* Each instance runs on a dedicated thread and is responsible for receiving {@link NetworkEvent} from its client and adding them
* to the {@link #actionQueue}.
* It is also responsible to send events and game model updates back to the client.
*/
public class ClientHandler implements Runnable {
/** The TCP socket */
private Socket clientSocket;
private TCPServer server;
public ObjectInputStream in = null;
public ObjectOutputStream out = null;
/** Input stream used to receive objects from the client */
public ObjectInputStream in;
/** Output stream used to send objects to the client */
public ObjectOutputStream out;
/**
* Shared list of all client handlers.
* This handler removes itself from the list when disconnected.
*/
List<ClientHandler> clientHandlers;
/** Queue containing the events to be applied to the game model */
BlockingQueue<NetworkEvent> actionQueue;
private EventType eventType;
public Socket getClientSocket() {
return clientSocket;
}
/**
* Class constructor that initializes the attributes.
* @param clientSocket The socket representing the client's TCP connection
* @param clientHandlers The shared list of all active client handlers
* @param actionQueue The queue containing incoming events
*/
public ClientHandler(Socket clientSocket, List<ClientHandler> clientHandlers, BlockingQueue<NetworkEvent> actionQueue) {
this.clientSocket = clientSocket;
this.clientHandlers = clientHandlers;
this.actionQueue = actionQueue;
}
/**
* Listens for incoming {@link NetworkEvent}s from the client and adds them to the {@link #actionQueue}.
* Upon disconnection, this handler removes itself from {@link #clientHandlers}.
*/
@Override
public void run(){
clientLoop();
}
private void clientLoop(){
try{
NetworkEvent input = null;
synchronized(in){
in = new ObjectInputStream(clientSocket.getInputStream());
}
while(true){
try{
input = (NetworkEvent) (in.readObject());
if(!actionQueue.add(input)){
System.out.println("An error occurred in inserting an action into queue");
}
}
catch(java.io.IOException e){
e.printStackTrace();
}
catch (ClassNotFoundException e){
throw new RuntimeException(e);
public void run() {
try {
in = new ObjectInputStream(clientSocket.getInputStream());
out = new ObjectOutputStream(clientSocket.getOutputStream());
while (true) {
NetworkEvent event = (NetworkEvent) in.readObject();
if (!actionQueue.add(event)) {
System.out.println("Error inserting action into queue");
}
}
}
catch (IOException e) {
} catch (IOException e) {
clientHandlers.remove(this);
e.printStackTrace();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public void notifyEvent(NetworkEvent event){
synchronized(out){
try{
/**
* Sends a {@link NetworkEvent} to the client.
* @param event The network event to send to the client.
*/
public void notifyEvent(NetworkEvent event) {
synchronized (out) {
try {
out = new ObjectOutputStream(clientSocket.getOutputStream());
out.writeObject(event);
}
catch(IOException e){
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void notifyModel(Game game){
synchronized(out){
try{
/**
* Sends the current game model to this client.
* @param game The current state of the game to send to the client.
*/
public void notifyModel(Game game) {
synchronized (out) {
try {
out = new ObjectOutputStream(clientSocket.getOutputStream());
out.writeObject(game);
}
catch(IOException e){
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@@ -3,6 +3,7 @@ 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.Model.Game;
import it.polimi.ingsw.gc14.Network.EventType;
import it.polimi.ingsw.gc14.Network.NetworkEvent;
import it.polimi.ingsw.gc14.Network.NetworkEvents.AddPlayer;
@@ -12,71 +13,120 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
/**
* Server TCP. Accepts connections and manages all client handlers.
*/
public class TCPServer {
int port = -1;
int ConnectedPlayers = 0;
ServerSocket serverTCP = null;
GameController gameController;
/** TCP port */
int port;
/** Number of currently connected clients */
int ConnectedPlayers;
/** Socket TCP */
ServerSocket socketTCP;
/** Server game's controller */
GameController controller;
/** Queue containing the events to be applied to the game model */
BlockingQueue<NetworkEvent> actionQueue;
/**
* 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;
/** List containing all client's handlers */
private List<ClientHandler> clientHandlers;
private int getConnectedPlayers(){
return ConnectedPlayers;
/**
* 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){
this.port = port;
this.ConnectedPlayers = 0;
this.socketTCP = null;
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(){
clientHandlers = new ArrayList<>();
try{
serverTCP = new ServerSocket(port);
socketTCP = new ServerSocket(port);
}
catch (IOException e){
System.out.println("Could not listen on port: " + port);
System.out.println("Could not start the server TCP on port: " + port);
e.printStackTrace();
return;
}
System.out.println("Listening on port: " + port);
System.out.println("Server TCP started on port: " + port);
Socket clientSocket;
while(true){
Socket clientSocket = null;
try{
clientSocket = serverTCP.accept();
clientSocket = socketTCP.accept();
ObjectInputStream clientSocketObj = new ObjectInputStream(clientSocket.getInputStream());
NetworkEvent event = (NetworkEvent) clientSocketObj.readObject();
synchronized (gameController){
if(!(event instanceof AddPlayer) || (gameController.getModel() != null && gameController.getModel().getCurrentPlayerNumber() >= gameController.getModel().getNPlayers())){
clientSocket.getOutputStream().write((int)(-1));
if(!(event.getEventType() == EventType.ADD_PLAYER)){
clientSocket.getOutputStream().write((int)(-1));
clientSocket.close();
System.out.println("Invalid parameters. Connection terminated.\n");
}
else{
AddPlayer eventAddPlayer = (AddPlayer) event;
if (eventAddPlayer.getProposedNPlayer() < 2 || eventAddPlayer.getProposedNPlayer() > 5) {
clientSocket.getOutputStream().write((int) (-1));
clientSocket.close();
System.out.println("Invalid parameters. Connection terminated.\n");
}
else{
synchronized (gameController) {
AddPlayer addPlayer = (AddPlayer) event;
if(playerList.isEmpty() && (addPlayer.getProposedNPlayer() < 2 || addPlayer.getProposedNPlayer() > 5)){
clientSocket.getOutputStream().write((int)(-1));
clientSocket.close();
System.out.println("Invalid parameters. Connection terminated.\n");
}
else if(gameController.addPlayer(addPlayer.getUsername())){
if(playerList.isEmpty()){
Game game = new Game(addPlayer.getProposedNPlayer());
gameController.setModel(game);
playerList.setLimit(addPlayer.getProposedNPlayer());
}
playerList.add(addPlayer.getUsername());
clientSocket.getOutputStream().write((int)(1));
}
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(clientSocket, 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");
}
}
}
// gestione di ADD_PLAYER
}
catch(IOException e){
e.printStackTrace();
@@ -84,44 +134,17 @@ public class TCPServer {
catch(ClassNotFoundException e){
throw new RuntimeException(e);
}
System.out.println("Accepted player: " + gameController.getModel().getPlayerByUsername(clientSocket.getInetAddress().toString()));
ConnectedPlayers++;
ClientHandler clientHandler = new ClientHandler(clientSocket, clientHandlers, actionQueue);
clientHandlers.add(clientHandler);
//Sending model to clients
if(ConnectedPlayers == gameController.getModel().getNPlayers()){
for (ClientHandler handler : clientHandlers) {
try {
synchronized(handler.out){
ObjectOutputStream socketTx = new ObjectOutputStream(handler.getClientSocket().getOutputStream());
socketTx.writeObject(gameController.getModel());
}
}
catch(IOException e){
e.printStackTrace();
}
}
}
Thread t = new Thread(clientHandler);
t.start();
}
}
public TCPServer(GameController gameController, int port, BlockingQueue<NetworkEvent> actionQueue, LimitedList<String> players){
this.port = port;
this.gameController = gameController;
this.actionQueue = actionQueue;
this.playerList = players;
}
/** Sends an action to all TCP clients */
public void notifyAll(NetworkEvent event){
clientHandlers.forEach((x) -> x.notifyEvent(event));
}
/** Sends a game model to all TCP clients */
public void notifyAll(Game model){
clientHandlers.forEach((x) -> x.notifyModel(model));
}