439 lines
20 KiB
HTML
439 lines
20 KiB
HTML
|
|
|
|
|
|
<!DOCTYPE html>
|
|
<html id="htmlId">
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
|
|
<title>Coverage Report > TCPServer</title>
|
|
<style type="text/css">
|
|
@import "../../css/coverage.css";
|
|
@import "../../css/idea.min.css";
|
|
</style>
|
|
<script type="text/javascript" src="../../js/highlight.min.js"></script>
|
|
<script type="text/javascript" src="../../js/highlightjs-line-numbers.min.js"></script>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="content">
|
|
<div class="breadCrumbs">
|
|
Current scope: <a href="../../index.html">all classes</a>
|
|
<span class="separator">|</span>
|
|
<a href="../index.html">it.polimi.ingsw.gc14.Network.TCP.Server</a>
|
|
</div>
|
|
|
|
<h1>Coverage Summary for Class: TCPServer (it.polimi.ingsw.gc14.Network.TCP.Server)</h1>
|
|
|
|
<table class="coverageStats">
|
|
<tr>
|
|
<th class="name">Class</th>
|
|
<th class="coverageStat
|
|
">
|
|
Class, %
|
|
</th>
|
|
<th class="coverageStat
|
|
">
|
|
Method, %
|
|
</th>
|
|
<th class="coverageStat
|
|
">
|
|
Branch, %
|
|
</th>
|
|
<th class="coverageStat
|
|
">
|
|
Line, %
|
|
</th>
|
|
</tr>
|
|
<tr>
|
|
<td class="name">TCPServer</td>
|
|
<td class="coverageStat">
|
|
<span class="percent">
|
|
0%
|
|
</span>
|
|
<span class="absValue">
|
|
(0/1)
|
|
</span>
|
|
</td>
|
|
<td class="coverageStat">
|
|
<span class="percent">
|
|
0%
|
|
</span>
|
|
<span class="absValue">
|
|
(0/8)
|
|
</span>
|
|
</td>
|
|
<td class="coverageStat">
|
|
<span class="percent">
|
|
0%
|
|
</span>
|
|
<span class="absValue">
|
|
(0/28)
|
|
</span>
|
|
</td>
|
|
<td class="coverageStat">
|
|
<span class="percent">
|
|
0%
|
|
</span>
|
|
<span class="absValue">
|
|
(0/105)
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
|
|
</table>
|
|
|
|
<br/>
|
|
<br/>
|
|
|
|
|
|
<pre>
|
|
<code class="sourceCode" id="sourceCode"> package it.polimi.ingsw.gc14.Network.TCP.Server;
|
|
|
|
import it.polimi.ingsw.gc14.Controller.GameController;
|
|
import it.polimi.ingsw.gc14.ErrorType;
|
|
import it.polimi.ingsw.gc14.LimitedMap;
|
|
import it.polimi.ingsw.gc14.Model.Game;
|
|
import it.polimi.ingsw.gc14.Model.GamePackage.GameStages;
|
|
import it.polimi.ingsw.gc14.Model.MiniModel;
|
|
import it.polimi.ingsw.gc14.Network.EventType;
|
|
import it.polimi.ingsw.gc14.Network.NetworkConfig;
|
|
import it.polimi.ingsw.gc14.Network.NetworkEvent;
|
|
import it.polimi.ingsw.gc14.Network.NetworkEvents.AddPlayer;
|
|
import it.polimi.ingsw.gc14.Network.NetworkEvents.ReconnectPlayer;
|
|
|
|
import java.io.*;
|
|
import java.net.*;
|
|
import java.util.ArrayList;
|
|
import java.util.Map;
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
|
import java.util.concurrent.BlockingQueue;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.stream.Collectors;
|
|
|
|
/**
|
|
* TCP server responsible for accepting client connections,
|
|
* handling player registration and reconnection, and sending
|
|
* game updates to connected clients.
|
|
*
|
|
* <p>The server also manages a dedicated heartbeat channel used
|
|
* to detect disconnected clients and associate each heartbeat
|
|
* connection with the corresponding {@link ClientHandler}.
|
|
*/
|
|
public class TCPServer {
|
|
|
|
/**
|
|
* Main TCP port used for standard client-server communication.
|
|
*/
|
|
private int port;
|
|
|
|
/**
|
|
* TCP port dedicated to heartbeat communication.
|
|
*/
|
|
private int heartbeatPort;
|
|
|
|
/**
|
|
* Main server socket used to accept client connections.
|
|
*/
|
|
private ServerSocket socketTCP;
|
|
|
|
/**
|
|
* Server socket used to accept heartbeat connections.
|
|
*/
|
|
private ServerSocket heartbeatSocketTCP;
|
|
|
|
/**
|
|
* Game controller used to manage the server-side game logic.
|
|
*/
|
|
private final GameController controller;
|
|
|
|
/**
|
|
* Queue containing network events received from clients.
|
|
*/
|
|
private BlockingQueue<NetworkEvent> actionQueue;
|
|
|
|
/**
|
|
* Map storing the online/offline status of connected players.
|
|
*/
|
|
private LimitedMap<String, Boolean> playerList;
|
|
|
|
/**
|
|
* List of active TCP client handlers.
|
|
*/
|
|
private CopyOnWriteArrayList<ClientHandler> clientHandlers;
|
|
|
|
|
|
/**
|
|
* Temporary map associating each username with the corresponding
|
|
* {@link ClientHandler} waiting for its heartbeat connection.
|
|
*/
|
|
<b class="nc"> private final Map<String, ClientHandler> pendingHeartbeat = new ConcurrentHashMap<>();</b>
|
|
|
|
/**
|
|
* Constructs a TCP server with the required game and network components.
|
|
*
|
|
* @param controller the game controller used to manage the game logic.
|
|
* @param port the main TCP port used for client communication.
|
|
* @param heartbeatPort the TCP port dedicated to heartbeat connections.
|
|
* @param actionQueue the queue containing incoming network events.
|
|
* @param playerList the map storing the connection status of the players.
|
|
*/
|
|
public TCPServer(GameController controller,
|
|
int port,
|
|
int heartbeatPort,
|
|
BlockingQueue<NetworkEvent> actionQueue,
|
|
<b class="nc"> LimitedMap<String, Boolean> playerList) {</b>
|
|
<b class="nc"> this.port = port;</b>
|
|
<b class="nc"> this.heartbeatPort = heartbeatPort;</b>
|
|
<b class="nc"> this.controller = controller;</b>
|
|
<b class="nc"> this.actionQueue = actionQueue;</b>
|
|
<b class="nc"> this.playerList = playerList;</b>
|
|
<b class="nc"> this.clientHandlers = new CopyOnWriteArrayList<>();</b>
|
|
}
|
|
|
|
/**
|
|
* Starts the TCP server and begins accepting client connections.
|
|
*
|
|
* <p>The method opens both the main TCP server socket and the dedicated
|
|
* heartbeat server socket. It then starts a separate thread for heartbeat
|
|
* connections and continuously waits for new players or reconnecting clients.
|
|
*
|
|
* <p>When a new connection is received, the first event must be an
|
|
* {@link AddPlayer} request. Depending on the current server state,
|
|
* the connection is handled either as a new player joining the game
|
|
* or as a reconnection attempt.
|
|
*
|
|
*/
|
|
public void start() {
|
|
try {
|
|
<b class="nc"> socketTCP = new ServerSocket(port);</b>
|
|
<b class="nc"> heartbeatSocketTCP = new ServerSocket(heartbeatPort);</b>
|
|
} catch (IOException e) {
|
|
<b class="nc"> System.out.println("Could not start TCP server");</b>
|
|
<b class="nc"> e.printStackTrace();</b>
|
|
return;
|
|
}
|
|
|
|
<b class="nc"> System.out.println("TCP server started on port: " + port);</b>
|
|
<b class="nc"> System.out.println("Heartbeat server started on port: " + heartbeatPort);</b>
|
|
|
|
<b class="nc"> new Thread(this::acceptHeartbeat, "heartbeat-acceptor").start();</b>
|
|
|
|
while (true) {
|
|
try {
|
|
<b class="nc"> Socket clientSocket = socketTCP.accept();</b>
|
|
|
|
<b class="nc"> ObjectOutputStream clientSend = new ObjectOutputStream(clientSocket.getOutputStream());</b>
|
|
<b class="nc"> clientSend.flush();</b>
|
|
<b class="nc"> ObjectInputStream clientReceive = new ObjectInputStream(clientSocket.getInputStream());</b>
|
|
|
|
<b class="nc"> NetworkEvent event = (NetworkEvent) clientReceive.readObject();</b>
|
|
|
|
<b class="nc"> if (!(event.getEventType() == EventType.ADD_PLAYER)) {</b>
|
|
clientSocket.close();
|
|
<b class="nc"> System.out.println("Invalid parameters. Connection terminated.");</b>
|
|
continue;
|
|
}
|
|
|
|
<b class="nc"> AddPlayer eventAddPlayer = (AddPlayer) event;</b>
|
|
|
|
<b class="nc"> if (eventAddPlayer.getProposedNPlayer() < NetworkConfig.MIN_PLAYERS</b>
|
|
<b class="nc"> || eventAddPlayer.getProposedNPlayer() > NetworkConfig.MAX_PLAYERS) {</b>
|
|
<b class="nc"> eventAddPlayer.setErrorType(ErrorType.WRONG_PLAYER_NUMBER);</b>
|
|
<b class="nc"> eventAddPlayer.setIsError(true);</b>
|
|
<b class="nc"> clientSend.writeObject(eventAddPlayer);</b>
|
|
clientSocket.close();
|
|
<b class="nc"> System.out.println("Invalid parameters. Connection terminated.");</b>
|
|
continue;
|
|
}
|
|
|
|
<b class="nc"> synchronized (controller) {</b>
|
|
<b class="nc"> String username = eventAddPlayer.getUsername();</b>
|
|
//reconnect players after a server crash
|
|
<b class="nc"> if (playerList.isEmpty()&& controller.getModel()==null) {</b>
|
|
<b class="nc"> Game model = new Game(eventAddPlayer.getProposedNPlayer());</b>
|
|
<b class="nc"> controller.setModel(model);</b>
|
|
<b class="nc"> playerList.setLimit(eventAddPlayer.getProposedNPlayer());</b>
|
|
<b class="nc"> System.out.println("Game Created With :"+eventAddPlayer.getProposedNPlayer()+" Players");</b>
|
|
}
|
|
<b class="nc"> if(controller.getModel().getCurrentState().getGameStage()!= GameStages.WAITING ) {</b>
|
|
<b class="nc"> if (controller.getModel().getPlayers().stream().noneMatch(p -> p.getUserName().equals(username))|| controller.getModel().getCurrentState().getGameStage()==GameStages.ENDED) {</b>
|
|
<b class="nc"> eventAddPlayer.setErrorType(ErrorType.GAME_ALREADY_STARTED);</b>
|
|
<b class="nc"> eventAddPlayer.setIsError(true);</b>
|
|
<b class="nc"> clientSend.writeObject(eventAddPlayer);</b>
|
|
clientSocket.close();
|
|
<b class="nc"> System.out.println("Invalid parameters. Connection terminated.");</b>
|
|
<b class="nc"> continue;</b>
|
|
}
|
|
}
|
|
else {
|
|
<b class="nc"> if (controller.addPlayer(username)) {</b>
|
|
<b class="nc"> playerList.put(username, true);</b>
|
|
<b class="nc"> System.out.println("Accepted player: " + username);</b>
|
|
<b class="nc"> clientSend.writeObject(eventAddPlayer);</b>
|
|
<b class="nc"> ClientHandler handler = createAndRegisterHandler(</b>
|
|
username, clientSocket, clientSend, clientReceive);
|
|
<b class="nc"> new Thread(handler).start();</b>
|
|
<b class="nc"> continue;</b>
|
|
} else {
|
|
<b class="nc"> eventAddPlayer.setErrorType(ErrorType.USERNAME_ALREADY_USED);</b>
|
|
<b class="nc"> eventAddPlayer.setIsError(true);</b>
|
|
<b class="nc"> clientSend.writeObject(eventAddPlayer);</b>
|
|
clientSocket.close();
|
|
<b class="nc"> System.out.println("Invalid parameters. Connection terminated.");</b>
|
|
<b class="nc"> continue;</b>
|
|
}
|
|
}
|
|
|
|
<b class="nc"> if (!playerList.containsKey(username)) {</b>
|
|
<b class="nc"> playerList.put(username, true);</b>
|
|
<b class="nc"> System.out.println("(After crash)Reconnected player: " + username);</b>
|
|
<b class="nc"> clientSend.writeObject(eventAddPlayer);</b>
|
|
<b class="nc"> ClientHandler handler = createAndRegisterHandler(</b>
|
|
username, clientSocket, clientSend, clientReceive);
|
|
<b class="nc"> new Thread(handler).start();</b>
|
|
<b class="nc"> } else if (!playerList.get(username)) {</b>
|
|
// reconnect a previously disconnected player
|
|
<b class="nc"> playerList.put(username, true);</b>
|
|
<b class="nc"> System.out.println("Reconnected player: " + username);</b>
|
|
<b class="nc"> clientSend.writeObject(eventAddPlayer);</b>
|
|
<b class="nc"> ClientHandler handler = createAndRegisterHandler(</b>
|
|
username, clientSocket, clientSend, clientReceive);
|
|
<b class="nc"> Game game = controller.getModel();</b>
|
|
<b class="nc"> handler.notifyMiniModel(new MiniModel(</b>
|
|
<b class="nc"> game.getSlotMap(), game.getOrderLogicCard(),</b>
|
|
<b class="nc"> game.getCurrentState(), game.getPlayers(),</b>
|
|
<b class="nc"> game.getAvailableTotems(),</b>
|
|
<b class="nc"> game.getUpperListTribeCards(), game.getLowerListTribeCards(),</b>
|
|
<b class="nc"> game.getUpperListBuilding(), game.getLowerListBuilding(),</b>
|
|
<b class="nc"> disconnectedUsernames(game)));</b>
|
|
<b class="nc"> new Thread(handler).start();</b>
|
|
<b class="nc"> actionQueue.add(new ReconnectPlayer(username));</b>
|
|
} else {
|
|
<b class="nc"> eventAddPlayer.setErrorType(ErrorType.USER_ALREADY_CONNECTED);</b>
|
|
<b class="nc"> eventAddPlayer.setIsError(true);</b>
|
|
<b class="nc"> clientSend.writeObject(eventAddPlayer);</b>
|
|
clientSocket.close();
|
|
<b class="nc"> System.out.println("Invalid parameters. Connection terminated.");</b>
|
|
}
|
|
<b class="nc"> }</b>
|
|
} catch (IOException | ClassNotFoundException e) {
|
|
<b class="nc"> e.printStackTrace();</b>
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Accepts heartbeat connections and associates them with the correct client handler.
|
|
*
|
|
* <p>Each client immediately sends its username on the heartbeat channel.
|
|
* The method uses that username to retrieve the pending {@link ClientHandler}
|
|
* and starts a dedicated {@link HeartbeatHandler}. If no pending handler is found,
|
|
* the heartbeat socket is closed.
|
|
*/
|
|
private void acceptHeartbeat() {
|
|
while (true) {
|
|
try {
|
|
<b class="nc"> Socket hbSocket = heartbeatSocketTCP.accept();</b>
|
|
<b class="nc"> ObjectInputStream hbIn =</b>
|
|
<b class="nc"> new ObjectInputStream(hbSocket.getInputStream());</b>
|
|
|
|
<b class="nc"> String username = (String) hbIn.readObject();</b>
|
|
|
|
<b class="nc"> ClientHandler handler = pendingHeartbeat.remove(username);</b>
|
|
|
|
<b class="nc"> if (handler != null) {</b>
|
|
<b class="nc"> HeartbeatHandler hb =</b>
|
|
new HeartbeatHandler(username, hbSocket, handler);
|
|
|
|
<b class="nc"> new Thread(hb, "heartbeat-" + username).start();</b>
|
|
<b class="nc"> System.out.println("Heartbeat connected for: " + username);</b>
|
|
} else {
|
|
<b class="nc"> System.out.println(</b>
|
|
"No pending handler for: " + username + ", closing heartbeat."
|
|
);
|
|
hbSocket.close();
|
|
}
|
|
|
|
} catch (IOException | ClassNotFoundException e) {
|
|
<b class="nc"> e.printStackTrace();</b>
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notifies connected TCP clients of a new network event.
|
|
*
|
|
* <p>If the event does not represent an error, it is sent to all connected clients.
|
|
* If it represents an error, it is sent only to the client that requested the action.
|
|
*
|
|
* @param event the network event to send to the clients.
|
|
*/
|
|
public void notifyAll(NetworkEvent event) {
|
|
<b class="nc"> clientHandlers.forEach(h -> {</b>
|
|
<b class="nc"> if (!event.isError() || event.getUsername().equals(h.getUsername())) {</b>
|
|
<b class="nc"> h.notifyEvent(event);</b>
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Notifies all connected TCP clients of a new game model.
|
|
*
|
|
* @param model the updated mini model to send to the clients.
|
|
*/
|
|
public void notifyAll(MiniModel model) {
|
|
<b class="nc"> clientHandlers.forEach(h -> h.notifyMiniModel(model));</b>
|
|
}
|
|
|
|
/**
|
|
* Creates a {@link ClientHandler}, registers it in {@code pendingHeartbeat}
|
|
* and {@code clientHandlers}, but does NOT start its thread — callers start
|
|
* the thread after any extra setup (e.g. sending a model snapshot).
|
|
*/
|
|
private ClientHandler createAndRegisterHandler(String username,
|
|
Socket socket,
|
|
ObjectOutputStream out,
|
|
ObjectInputStream in) {
|
|
<b class="nc"> ClientHandler handler = new ClientHandler(</b>
|
|
username, socket, out, in, clientHandlers, playerList, actionQueue);
|
|
<b class="nc"> pendingHeartbeat.put(username, handler);</b>
|
|
<b class="nc"> clientHandlers.add(handler);</b>
|
|
<b class="nc"> return handler;</b>
|
|
}
|
|
|
|
/** Returns usernames of players currently marked as disconnected in the given game. */
|
|
private static ArrayList<String> disconnectedUsernames(Game game) {
|
|
<b class="nc"> return game.getDisconnectedPlayers().entrySet().stream()</b>
|
|
<b class="nc"> .filter(Map.Entry::getValue)</b>
|
|
<b class="nc"> .map(e -> e.getKey().getUserName())</b>
|
|
<b class="nc"> .collect(Collectors.toCollection(ArrayList::new));</b>
|
|
}
|
|
}
|
|
</code>
|
|
</pre>
|
|
</div>
|
|
|
|
<script type="text/javascript">
|
|
(function() {
|
|
var msie = false, msie9 = false;
|
|
/*@cc_on
|
|
msie = true;
|
|
@if (@_jscript_version >= 9)
|
|
msie9 = true;
|
|
@end
|
|
@*/
|
|
|
|
if (!msie || msie && msie9) {
|
|
hljs.highlightAll()
|
|
hljs.initLineNumbersOnLoad();
|
|
}
|
|
})();
|
|
</script>
|
|
|
|
<div class="footer">
|
|
|
|
<div style="float:right;">generated on 2026-06-14 21:53</div>
|
|
</div>
|
|
</body>
|
|
</html>
|