|
You last visited: Today at 20:42
Advertisement
Java Übungsaufgabe: Netzwerke
Discussion on Java Übungsaufgabe: Netzwerke within the Java forum part of the Coders Den category.
02/09/2017, 13:39
|
#1
|
elite*gold: 0
Join Date: Aug 2010
Posts: 307
Received Thanks: 73
|
Java Übungsaufgabe: Netzwerke
Moin,
ich habe die Übungsaufgabe bekommen, ein "Quiz-Duell"-Spiel mit einem Netzwerk bestehend aus Client und Server zu erstellen.
Nun soll ich eine Methode play(), die eine Verbindung zum Server aufbaut, einen BufferedReader für Eingaben aus dem Netzwerk, einen PrintWriter für Ausgaben in das Netzwerk und noch einen Bufferedreader für Konsolenausgaben erzeugt, erstellen.
Ich bin nur leider vollkommen ratlos wie ich das anstellen soll und bitte deshalb um Hilfe. Der GameServer ist gegeben.
Code vom GameServer:
Code:
package piquizduell;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.List;
/**
* Realisiert den Netzwerk-Server für ein PIQuizDuell.
*
* [MENTION=1332190]author[/MENTION] K. Hölscher, S. Offermann
* [MENTION=1805674]Version[/MENTION] 2017-01-19
*/
final class GameServer implements Runnable {
/**
* Der Port, an dem der Netzwerkserver im Standardfall auf Verbindungen horcht.
*/
private static final int PORT = 4444;
/**
* Die Anzahl an Verbindungen, die im Standardfall maximal gleichzeitig mit diesem Server
* bestehen können.
*/
private static final int MAX_CONNECTIONS = 10;
/**
* Spielernamen-Muster. Erlaubt sind alle Zeichenketten, die mit einem Großbuchstaben
* beginnen, gefolgt von beliebig vielen Kleinbuchstaben. Gleichzeitig muss aber die
* minimale Namenslänge {@linkplain NAMELENGTH_MINIMUM} eingehalten werden.
*/
private static final String PLAYERNAME_PATTERN = "[A-Z][a-z0-9]*";
/**
* Minimale Namenslänge, die akzeptiert wird. Kürzere Namen werden abgelehnt.
*/
private static final int NAMELENGTH_MINIMUM = 3;
/**
* Zeit (in Millisekunden) zwischen zwei Durchläufen der Hauptschleife.
*/
private static final int MAINLOOP_INTERVALL = 250;
/**
* Der Charset, der beim Laden, Speichern und Parsen verwendet wird.
*/
public static final String CHARSET = "UTF-8";
/**
* Der Name des Spieles. Wird für die Begrüßung bei neuen Verbindungen verwendet.
*/
public static final String GAME_NAME = "PI-Quiz-Duell";
/**
* Standardwert für die maximale Dauer einer Fragerunde.
*/
public static final int DEFAULT_ROUND_TIMEOUT = 30;
/**
* Der Port, an dem dieser Netzwerkserver auf eingehende Verbindungen horcht.
*/
private final int port;
/**
* Die Anzahl der maximal erlaubten gleichzeitigen Verbindungen mit diesem Netzwerkserver.
*/
private final int maxConnections;
/**
* Eine Liste mit Verbindungen zu konkreten Clients. Zugriff muss synchronisiert werden, da
* verschiedene Threads Zugriff darauf haben.
*/
private final List<ClientConnection> clientConnections;
/**
* Der Monitor zum Sperren des Zugriffs auf die Liste der Client-Verbindungen.
*/
private ReentrantReadWriteLock clientListLock;
/**
* Read-Lock für den lesenden Zugriff auf die Liste der Client-Verbindungen.
*/
private Lock clientListReadLock;
/**
* Write-Lock für den schreibenden Zugriff auf die Liste der Client-Verbindungen.
*/
private Lock clientListWriteLock;
/**
* Zeigt an, ob das Spiel beendet werden soll oder nicht. Wird von zwei verschiedenen
* Threads verwendet und ist daher als atomare Variable deklariert.
*/
private AtomicBoolean shutdown;
/**
* Das Basisduell, das diesen Server verwendet.
*/
private final PIQuizDuellBase game;
/**
* Maximale Dauer einer Fragerunde.
*/
private int roundTimeout;
/**
* Zeitpunkt, an dem die aktuelle Fragerunde gestartet wurde.
*/
private long roundStartTime;
/**
* Erzeugt einen neuen Netzwerkserver mit der Standardkonfiguration (siehe finale
* Attribute) und dem gegebenen Basisspiel.
*
* [MENTION=1985011]param[/MENTION] game Das Basisspiel, für das dieser Netzwerkserver die Kommunikation übernimmt.
*/
GameServer(final PIQuizDuellBase game) {
this(PORT, MAX_CONNECTIONS, game);
}
/**
* Erzeugt einen neuen Netzwerkserver mit einer Konfiguration, die sich aus den Parametern
* ergibt.
*
* [MENTION=1985011]param[/MENTION] pPort Der Port, an dem dieser Netzwerkserver auf eingehende
* Verbindungen horcht.
* [MENTION=1985011]param[/MENTION] pMaxConnections Die maximal erlaubte Anzahl gleichzeitiger Verbindungen für
* diesen Netzwerkserver.
* [MENTION=1985011]param[/MENTION] pGame Das Basisspiel, für das dieser Netzwerkserver die Kommunikation
* übernimmt.
*/
GameServer(final int pPort, final int pMaxConnections, final PIQuizDuellBase pGame) {
port = pPort;
maxConnections = pMaxConnections;
clientConnections = new ArrayList<>();
clientListLock = new ReentrantReadWriteLock();
clientListReadLock = clientListLock.readLock();
clientListWriteLock = clientListLock.writeLock();
shutdown = new AtomicBoolean(false);
game = pGame;
setRoundTimeout(DEFAULT_ROUND_TIMEOUT);
roundStartTime = 0;
}
/**
* Startet diesen Netzwerkserver.
*/
void start() {
final Thread thread = new Thread(this);
thread.setDaemon(true);
thread.start();
mainLoop();
}
/**
* Aktiviert den Schalter, der angibt, dass der Netzwerkserver beendet werden soll.
*/
void shutdown() {
shutdown.set(true);
}
/**
* Kümmert sich um eine neue Verbindung. Es wird ein Writer erzeugt, mit dem ins Netzwerk
* geschrieben werden kann und eine Begrüßungsnachricht darüber versendet. Falls bereits
* die maximale zulässige Zahl von Verbindungen besteht, wird die Verbindung abgelehnt und
* der Socket geschlossen.
*
* [MENTION=1985011]param[/MENTION] socket Der Socket, der die neue Verbindung repräsentiert.
*
* [MENTION=5770464]Throws[/MENTION] IOException Falls bei der Netzwerkkommunikation ein Fehler auftritt.
*/
private void handleNewConnection(final Socket socket) throws IOException {
PrintWriter out;
BufferedReader in;
try {
out = new PrintWriter(socket.getOutputStream());
in = new BufferedReader(new InputStreamReader(
socket.getInputStream(), CHARSET));
} catch (final IOException e) {
System.err.println(
"An error occured while trying to get input/output "
+ "streams for new connection. Connection closed.");
closeSocket(socket);
return;
}
final int numberOfConnections = getNumberOfConnections();
if (numberOfConnections > maxConnections) {
out.print("Die maximale Anzahl von Verbindungen ist bereits erreicht.");
out.println(" Versuche es später noch einmal!");
out.flush();
System.out.println(
"Rejected connection: Number of players exceeded ("
+ numberOfConnections + ")");
closeSocket(socket);
} else if (game.quizInProgress()) {
out.print("Es wird gerade ein Quiz gespielt.");
out.println(" Versuche es später noch einmal!");
out.flush();
System.out.println(
"Rejected connection: Quiz in progress!");
closeSocket(socket);
} else {
System.out.println(
"Connection accepted. Current number of connections is "
+ (numberOfConnections + 1));
final ClientConnection newConnection
= new ClientConnection(socket, out, in);
addConnection(newConnection);
newConnection.send("Willkommen zu " + GAME_NAME + "!"
+ ClientConnection.NEWLINE);
newConnection.setState(ClientConnection.STATE.GREETED);
}
}
/**
* Schließt den gegebenen Socket und damit alle damit verbundenen Streams. Falls dabei eine
* Exception auftritt, wird diese auf der Konsole ausgegeben.
*
* [MENTION=1985011]param[/MENTION] socket Der zu schließende Socket.
*/
private void closeSocket(final Socket socket) {
try {
socket.close();
} catch (final IOException e) {
// wir können nichts mehr tun außer zu informieren
System.err.println(
"IO-Exception while trying to close the socket.");
System.err.println(e.getMessage());
}
}
/**
* Fügt die gegebene Client-Verbindung zur Liste der Verbindungen hinzu. Da es sich um
* einen schreibenden Zugriff handelt und mehr als ein Thread Zugriff auf die Liste der
* Verbindungen hat, wird ein Write-Lock gesetzt.
*
* Die Methode kann nur innerhalb des selben Pakets verwendet werden, daher wird der
* Parameter nicht auf Plausibilität überprüft!
*
* [MENTION=1985011]param[/MENTION] newConnection Die Verbindung, die hinzugefügt werden soll.
*/
void addConnection(final ClientConnection newConnection) {
clientListWriteLock.lock();
try {
clientConnections.add(newConnection);
} finally {
clientListWriteLock.unlock();
}
}
/**
* Entfernt die gegebene Client-Verbindung aus der Liste der Verbindungen. Da es sich um
* einen schreibenden Zugriff handelt und mehr als ein Thread Zugriff auf die Liste der
* Verbindungen hat, wird ein Write-Lock gesetzt.
*
* Die Methode kann nur innerhalb desselben Pakets verwendet werden, daher wird der
* Parameter nicht auf Plausibilität überprüft!
*
* [MENTION=1985011]param[/MENTION] clientConnection Die Verbindung, die entfernt werden soll.
*/
void remove(final ClientConnection clientConnection) {
clientListWriteLock.lock();
try {
clientConnections.remove(clientConnection);
} finally {
clientListWriteLock.unlock();
}
}
/**
* Gibt die Anzahl der aktuell bestehenden Verbindungen mit konkreten Clients zurück. Da es
* sich um einen lesenden Zugriff auf die Liste der Verbindungen handelt, der von mehreren
* Threads aus ausgeführt werden kann, wird ein Read-Lock verwendet.
*
* [MENTION=326673]return[/MENTION] Die Anzahl aktuell bestehender Verbindungen mit einem konkreten Client.
*/
int getNumberOfConnections() {
clientListReadLock.lock();
try {
return clientConnections.size();
} finally {
clientListReadLock.unlock();
}
}
/**
* Gibt die Client-Verbindung am gegebenen Index zurück.
*
* Da es sich um einen lesenden Zugriff auf die Liste der Verbindungen handelt, der von
* mehreren Threads aus ausgeführt werden kann, wird ein Read-Lock verwendet.
*
* Die Methode kann nur innerhalb des selben Pakets verwendet werden, daher wird der
* Parameter nicht auf Plausibilität überprüft!
*
* [MENTION=1985011]param[/MENTION] index Der Index der gesuchten Client-Verbindung.
*
* [MENTION=326673]return[/MENTION] Die Client-Verbindung, die sich am gegebenen Index befindet.
*/
ClientConnection getConnection(final int index) {
clientListReadLock.lock();
try {
return clientConnections.get(index);
} finally {
clientListReadLock.unlock();
}
}
/**
* Diese Methode wird in einem zweiten Thread ausgeführt und horcht auf eingehende
* Verbindungen. Wenn eine Verbindung eingeht, wird eine neue aber noch nicht mit einer
* SpielerIn assoziierte Client-Verbindung erstellt und eine Begrüßungszeichenkette
* gesendet. Dann wird die Verbindungsliste gesperrt und die neue Verbindung dieser Liste
* hinzugefügt. Anschließend wird die Verbindungsliste wieder freigegeben. Falls bereits
* die maximale Anzahl von SpielerInnen verbunden ist oder bereits ein Spiel gestartet
* wurde, werden keine neuen Verbindungen mehr entgegengenommen.
*
* Diese Methode wird in einem Thread ausgeführt, der parallel zum Thread mit der
* Hauptschleife läuft. Die Zugriffe auf alle Variablen, die in beiden Threads verwendet
* werden, müssen daher synchronisiert werden!
*/
[MENTION=295804]Override[/MENTION]
public void run() {
ServerSocket serverSocket = null;
try {
try {
serverSocket = new ServerSocket(port);
} catch (final IOException e) {
System.err.println(
"An error occured while trying to create the server "
+ "socket! Shutting down the game.");
System.err.println(e.getMessage());
shutdown.set(true);
return;
}
Socket socket;
while (!shutdown.get()) {
System.out.println("Waiting for connections.");
try {
socket = serverSocket.accept();
} catch (final IOException e) {
System.err.println(
"An error occured while trying to accept a "
+ "connection!");
System.err.println(e.getMessage());
shutdown.set(true);
return;
}
System.out.println("Connection accepted from "
+ socket.getInetAddress());
try {
handleNewConnection(socket);
} catch (final IOException e) {
System.err.println(
"Error while closing the socket. Ignored.");
System.err.println(e.getMessage());
}
}
} finally {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (final IOException e) {
// wir können eh nichts mehr machen
System.err.println(
"IOException while closing the server socket. "
+ "Doesn't matter.");
}
}
}
}
/**
* Die Hauptschleife, in der das Spiel abläuft. Diese Schleife läuft solange, bis eine
* SpielerIn "shutdown" eingibt und das Spiel damit herunterfährt. In dieser Schleife wird
* immer wieder der Reihe nach abgefragt, ob es eine Eingabe von einer Client-Verbindung
* gibt.
*
* Diese Hauptschleife läuft in einem Thread parallel zum Thread, der auf eingehende
* Verbindungen horcht. Die Zugriffe auf alle Variablen, die in beiden Threads verwendet
* werden, müssen daher synchronisiert werden!
*
* Eine hier abgebildete Spielrunde mit Abfragen an alle SpielerInnen und entsprechenden
* Reaktionen soll mindestens 250ms ({@linkplain GameServer#MAINLOOP_INTERVALL}) dauern.
* Nach der Abfrage aller SpielerInnen legt sich dieser Thread daher entsprechend lange
* schlafen.
*/
private void mainLoop() {
while (!shutdown.get()) {
final long startTime = System.currentTimeMillis();
int numberOfConnections = getNumberOfConnections();
for (int i = 0; i < numberOfConnections; i++) {
final ClientConnection clientConnection = getConnection(i);
// Input vom Client besorgen
String input = null;
try {
input = clientConnection.getInput();
} catch (final IOException e) {
System.err.println(
"An error occured while reading input from connection "
+ clientConnection.toString()
+ ". Scheduled to be removed.");
clientConnection.setState(ClientConnection.STATE.DEAD);
}
switch (clientConnection.getState()) {
case GREETED:
clientConnection.send("Bitte gib deinen Namen ein:");
clientConnection.setState(ClientConnection.STATE.INPUT_NAME);
break;
case INPUT_NAME:
if (clientConnection.timeout()) {
System.out.println(
"Connection "
+ clientConnection.toString()
+ " timed out "
+ "while waiting for name. Scheduled to be removed.");
clientConnection
.send("Das dauert zu lange. Schönen Tag.");
clientConnection.setState(ClientConnection.STATE.DEAD);
} else if (input != null) {
handleInput(clientConnection, input);
}
break;
case WAITING_FOR_START:
case PLAYING:
if (input != null) {
handleInput(clientConnection, input);
}
break;
default:
break;
}
}
/*
* Fehlerhafte Client-Verbindungen entfernen. Inzwischen durch accept
* angenomme Verbindungen werden hinten an die Liste angehängt und können zu
* diesem Zeitpunkt nicht fehlerhaft sein. Daher ist es ok die Liste nur bis
* zum oben verwendeten Index zu durchsuchen.
*/
for (int i = 0; i < numberOfConnections; i++) {
final ClientConnection clientConnection = getConnection(i);
if (clientConnection.getState()
== ClientConnection.STATE.DEAD) {
removeConnection(clientConnection);
i--;
numberOfConnections--;
}
}
/**
* Die maximale Zeit für eine Runde ist erreicht.
*/
if (roundStartTime > 0 && roundTimeout + roundStartTime <= startTime) {
roundStartTime = 0;
game.roundTimeout();
}
/* MindestZeit für eine Spielrunde einhalten */
final long elapsedTime = System.currentTimeMillis() - startTime;
if (elapsedTime < MAINLOOP_INTERVALL) {
try {
Thread.sleep(MAINLOOP_INTERVALL - elapsedTime);
} catch (final InterruptedException e) {
/* sollte nicht passieren, da wir keine interrupts auslösen */
System.err.println("InterruptedException in Mainloop: " + e.getMessage());
}
}
}
}
/**
* Entfernt die gegebene Client-Verbindung aus der Liste der Client-Verbindungen und
* gegebenenfalls die SpielerIn aus der SpielerInnen-Liste des Basisspiels. Ruft die
* Methode {@linkplain ClientConnection#dispose()} auf der Client-Verbindung auf, um
* eventuell verwendete Ressourcen freizugeben.
*
* [MENTION=1985011]param[/MENTION] clientConnection Die zu entfernende Client-Verbindung.
*/
private void removeConnection(final ClientConnection clientConnection) {
System.err.println("Connection "
+ clientConnection.toString() + " closed and removed.");
remove(clientConnection);
final BasePlayer player = clientConnection.getPlayer();
if (player != null) {
game.removePlayer(player);
}
clientConnection.dispose();
}
/**
* Verarbeitet die gegebene Eingabe für die gegebene Client-Verbindung.
*
* [MENTION=1985011]param[/MENTION] connection Die Client-Verbindung, von der die Eingabe stammt.
* [MENTION=1985011]param[/MENTION] input Die Eingabe, die verarbeitet werden soll.
*/
private void handleInput(final ClientConnection connection, final String input) {
switch (connection.getState()) {
case PLAYING:
game.input(connection.getPlayer(), input);
break;
case INPUT_NAME:
connection.resetTimeout();
final String playerName = input;
if (!playerName.matches(PLAYERNAME_PATTERN)
|| playerName.length() < NAMELENGTH_MINIMUM) {
connection.send("Das ist kein akzeptierter Name.");
connection.send("Ein Name muss mit einem Großbuchstaben (A-Z) beginnen"
+ " gefolgt von mindestens " + (NAMELENGTH_MINIMUM - 1)
+ " Kleinbuchstaben (a-z) oder Ziffern (0-9)!");
connection.send("Versuch es nochmal.");
connection.setState(ClientConnection.STATE.GREETED);
} else if (game.getPlayerByName(playerName) != null) {
connection.send("Dieser Name wird bereits verwendet. "
+ " Probiere einen anderen Namen.");
connection.setState(ClientConnection.STATE.GREETED);
} else {
final BasePlayer newPlayer = new BasePlayer(playerName, connection);
connection.setPlayer(newPlayer);
connection.setState(ClientConnection.STATE.WAITING_FOR_START);
newPlayer.send(String.format("Willkomen %s! Viel Spaß!", playerName));
System.out.println("Spieler " + playerName + " beigetreten.");
game.addPlayer(newPlayer);
}
break;
case WAITING_FOR_START:
final BasePlayer player = connection.getPlayer();
if (player.isQuizmaster() && input.equals("start")) {
game.startQuiz();
} else {
game.broadcast(player, String.format("%s: %s", player.getName(), input));
}
default:
break;
}
}
/**
* Setzt die maximale Dauer einer Fragerunde auf die gegebene Anzahl in Sekunden.
*
* !!!Da die Methode nur im selben Paket aufgerufen werden kann, wird das Argument nicht
* auf Plausibilität geprüft!!!
*
* [MENTION=1985011]param[/MENTION] seconds Die Anzahl der Sekunden, die eine Fragerunde maximal dauert.
*/
void setRoundTimeout(final int seconds) {
roundTimeout = seconds * 1000;
}
/**
* Setzt den Timer für die Rundendauer zurück.
*/
void startRound() {
roundStartTime = System.currentTimeMillis();
}
}
Ich hoffe ihr könnt mir behilflich sein.
MfG challenger77
|
|
|
02/09/2017, 14:26
|
#2
|
elite*gold: 203
Join Date: Sep 2007
Posts: 732
Received Thanks: 190
|
Ich schätze mal du willst den Client programmieren.
Der Client baut ein Socket zum Server auf, mit dem er dann kommunizieren kann. Die Kommunikation findet über die genannten Input- und Output-Streams statt.
Den GameServer hab ich mir jetzt nicht angeschaut, aber wenn dieser eine Art Protokoll hat, dann musst du dieses beachten.
Wie man dir sonst genauer helfen kann weiss ich auch nicht^^
|
|
|
02/09/2017, 15:36
|
#3
|
elite*gold: 34
Join Date: Apr 2011
Posts: 1,475
Received Thanks: 1,227
|
Wo genau brauchst du jetzt hilfe bzw. was genau verstehst du nicht?
|
|
|
02/09/2017, 20:58
|
#4
|
elite*gold: 0
Join Date: Aug 2010
Posts: 307
Received Thanks: 73
|
@ genau danach hab ich gesucht. Danke.
|
|
|
02/11/2017, 12:39
|
#5
|
elite*gold: 760
Join Date: Apr 2010
Posts: 8,358
Received Thanks: 1,673
|
Quote:
Originally Posted by challenger77
Moin,
ich habe die Übungsaufgabe bekommen, ein "Quiz-Duell"-Spiel mit einem Netzwerk bestehend aus Client und Server zu erstellen.
Nun soll ich eine Methode play(), die eine Verbindung zum Server aufbaut, einen BufferedReader für Eingaben aus dem Netzwerk, einen PrintWriter für Ausgaben in das Netzwerk und noch einen Bufferedreader für Konsolenausgaben erzeugt, erstellen.
Ich bin nur leider vollkommen ratlos wie ich das anstellen soll und bitte deshalb um Hilfe. Der GameServer ist gegeben.
Code vom GameServer:
Code:
package piquizduell;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.List;
/**
* Realisiert den Netzwerk-Server für ein PIQuizDuell.
*
* [MENTION=1332190]author[/MENTION] K. Hölscher, S. Offermann
* [MENTION=1805674]Version[/MENTION] 2017-01-19
*/
final class GameServer implements Runnable {
/**
* Der Port, an dem der Netzwerkserver im Standardfall auf Verbindungen horcht.
*/
private static final int PORT = 4444;
/**
* Die Anzahl an Verbindungen, die im Standardfall maximal gleichzeitig mit diesem Server
* bestehen können.
*/
private static final int MAX_CONNECTIONS = 10;
/**
* Spielernamen-Muster. Erlaubt sind alle Zeichenketten, die mit einem Großbuchstaben
* beginnen, gefolgt von beliebig vielen Kleinbuchstaben. Gleichzeitig muss aber die
* minimale Namenslänge {@linkplain NAMELENGTH_MINIMUM} eingehalten werden.
*/
private static final String PLAYERNAME_PATTERN = "[A-Z][a-z0-9]*";
/**
* Minimale Namenslänge, die akzeptiert wird. Kürzere Namen werden abgelehnt.
*/
private static final int NAMELENGTH_MINIMUM = 3;
/**
* Zeit (in Millisekunden) zwischen zwei Durchläufen der Hauptschleife.
*/
private static final int MAINLOOP_INTERVALL = 250;
/**
* Der Charset, der beim Laden, Speichern und Parsen verwendet wird.
*/
public static final String CHARSET = "UTF-8";
/**
* Der Name des Spieles. Wird für die Begrüßung bei neuen Verbindungen verwendet.
*/
public static final String GAME_NAME = "PI-Quiz-Duell";
/**
* Standardwert für die maximale Dauer einer Fragerunde.
*/
public static final int DEFAULT_ROUND_TIMEOUT = 30;
/**
* Der Port, an dem dieser Netzwerkserver auf eingehende Verbindungen horcht.
*/
private final int port;
/**
* Die Anzahl der maximal erlaubten gleichzeitigen Verbindungen mit diesem Netzwerkserver.
*/
private final int maxConnections;
/**
* Eine Liste mit Verbindungen zu konkreten Clients. Zugriff muss synchronisiert werden, da
* verschiedene Threads Zugriff darauf haben.
*/
private final List<ClientConnection> clientConnections;
/**
* Der Monitor zum Sperren des Zugriffs auf die Liste der Client-Verbindungen.
*/
private ReentrantReadWriteLock clientListLock;
/**
* Read-Lock für den lesenden Zugriff auf die Liste der Client-Verbindungen.
*/
private Lock clientListReadLock;
/**
* Write-Lock für den schreibenden Zugriff auf die Liste der Client-Verbindungen.
*/
private Lock clientListWriteLock;
/**
* Zeigt an, ob das Spiel beendet werden soll oder nicht. Wird von zwei verschiedenen
* Threads verwendet und ist daher als atomare Variable deklariert.
*/
private AtomicBoolean shutdown;
/**
* Das Basisduell, das diesen Server verwendet.
*/
private final PIQuizDuellBase game;
/**
* Maximale Dauer einer Fragerunde.
*/
private int roundTimeout;
/**
* Zeitpunkt, an dem die aktuelle Fragerunde gestartet wurde.
*/
private long roundStartTime;
/**
* Erzeugt einen neuen Netzwerkserver mit der Standardkonfiguration (siehe finale
* Attribute) und dem gegebenen Basisspiel.
*
* [MENTION=1985011]param[/MENTION] game Das Basisspiel, für das dieser Netzwerkserver die Kommunikation übernimmt.
*/
GameServer(final PIQuizDuellBase game) {
this(PORT, MAX_CONNECTIONS, game);
}
/**
* Erzeugt einen neuen Netzwerkserver mit einer Konfiguration, die sich aus den Parametern
* ergibt.
*
* [MENTION=1985011]param[/MENTION] pPort Der Port, an dem dieser Netzwerkserver auf eingehende
* Verbindungen horcht.
* [MENTION=1985011]param[/MENTION] pMaxConnections Die maximal erlaubte Anzahl gleichzeitiger Verbindungen für
* diesen Netzwerkserver.
* [MENTION=1985011]param[/MENTION] pGame Das Basisspiel, für das dieser Netzwerkserver die Kommunikation
* übernimmt.
*/
GameServer(final int pPort, final int pMaxConnections, final PIQuizDuellBase pGame) {
port = pPort;
maxConnections = pMaxConnections;
clientConnections = new ArrayList<>();
clientListLock = new ReentrantReadWriteLock();
clientListReadLock = clientListLock.readLock();
clientListWriteLock = clientListLock.writeLock();
shutdown = new AtomicBoolean(false);
game = pGame;
setRoundTimeout(DEFAULT_ROUND_TIMEOUT);
roundStartTime = 0;
}
/**
* Startet diesen Netzwerkserver.
*/
void start() {
final Thread thread = new Thread(this);
thread.setDaemon(true);
thread.start();
mainLoop();
}
/**
* Aktiviert den Schalter, der angibt, dass der Netzwerkserver beendet werden soll.
*/
void shutdown() {
shutdown.set(true);
}
/**
* Kümmert sich um eine neue Verbindung. Es wird ein Writer erzeugt, mit dem ins Netzwerk
* geschrieben werden kann und eine Begrüßungsnachricht darüber versendet. Falls bereits
* die maximale zulässige Zahl von Verbindungen besteht, wird die Verbindung abgelehnt und
* der Socket geschlossen.
*
* [MENTION=1985011]param[/MENTION] socket Der Socket, der die neue Verbindung repräsentiert.
*
* [MENTION=5770464]Throws[/MENTION] IOException Falls bei der Netzwerkkommunikation ein Fehler auftritt.
*/
private void handleNewConnection(final Socket socket) throws IOException {
PrintWriter out;
BufferedReader in;
try {
out = new PrintWriter(socket.getOutputStream());
in = new BufferedReader(new InputStreamReader(
socket.getInputStream(), CHARSET));
} catch (final IOException e) {
System.err.println(
"An error occured while trying to get input/output "
+ "streams for new connection. Connection closed.");
closeSocket(socket);
return;
}
final int numberOfConnections = getNumberOfConnections();
if (numberOfConnections > maxConnections) {
out.print("Die maximale Anzahl von Verbindungen ist bereits erreicht.");
out.println(" Versuche es später noch einmal!");
out.flush();
System.out.println(
"Rejected connection: Number of players exceeded ("
+ numberOfConnections + ")");
closeSocket(socket);
} else if (game.quizInProgress()) {
out.print("Es wird gerade ein Quiz gespielt.");
out.println(" Versuche es später noch einmal!");
out.flush();
System.out.println(
"Rejected connection: Quiz in progress!");
closeSocket(socket);
} else {
System.out.println(
"Connection accepted. Current number of connections is "
+ (numberOfConnections + 1));
final ClientConnection newConnection
= new ClientConnection(socket, out, in);
addConnection(newConnection);
newConnection.send("Willkommen zu " + GAME_NAME + "!"
+ ClientConnection.NEWLINE);
newConnection.setState(ClientConnection.STATE.GREETED);
}
}
/**
* Schließt den gegebenen Socket und damit alle damit verbundenen Streams. Falls dabei eine
* Exception auftritt, wird diese auf der Konsole ausgegeben.
*
* [MENTION=1985011]param[/MENTION] socket Der zu schließende Socket.
*/
private void closeSocket(final Socket socket) {
try {
socket.close();
} catch (final IOException e) {
// wir können nichts mehr tun außer zu informieren
System.err.println(
"IO-Exception while trying to close the socket.");
System.err.println(e.getMessage());
}
}
/**
* Fügt die gegebene Client-Verbindung zur Liste der Verbindungen hinzu. Da es sich um
* einen schreibenden Zugriff handelt und mehr als ein Thread Zugriff auf die Liste der
* Verbindungen hat, wird ein Write-Lock gesetzt.
*
* Die Methode kann nur innerhalb des selben Pakets verwendet werden, daher wird der
* Parameter nicht auf Plausibilität überprüft!
*
* [MENTION=1985011]param[/MENTION] newConnection Die Verbindung, die hinzugefügt werden soll.
*/
void addConnection(final ClientConnection newConnection) {
clientListWriteLock.lock();
try {
clientConnections.add(newConnection);
} finally {
clientListWriteLock.unlock();
}
}
/**
* Entfernt die gegebene Client-Verbindung aus der Liste der Verbindungen. Da es sich um
* einen schreibenden Zugriff handelt und mehr als ein Thread Zugriff auf die Liste der
* Verbindungen hat, wird ein Write-Lock gesetzt.
*
* Die Methode kann nur innerhalb desselben Pakets verwendet werden, daher wird der
* Parameter nicht auf Plausibilität überprüft!
*
* [MENTION=1985011]param[/MENTION] clientConnection Die Verbindung, die entfernt werden soll.
*/
void remove(final ClientConnection clientConnection) {
clientListWriteLock.lock();
try {
clientConnections.remove(clientConnection);
} finally {
clientListWriteLock.unlock();
}
}
/**
* Gibt die Anzahl der aktuell bestehenden Verbindungen mit konkreten Clients zurück. Da es
* sich um einen lesenden Zugriff auf die Liste der Verbindungen handelt, der von mehreren
* Threads aus ausgeführt werden kann, wird ein Read-Lock verwendet.
*
* [MENTION=326673]return[/MENTION] Die Anzahl aktuell bestehender Verbindungen mit einem konkreten Client.
*/
int getNumberOfConnections() {
clientListReadLock.lock();
try {
return clientConnections.size();
} finally {
clientListReadLock.unlock();
}
}
/**
* Gibt die Client-Verbindung am gegebenen Index zurück.
*
* Da es sich um einen lesenden Zugriff auf die Liste der Verbindungen handelt, der von
* mehreren Threads aus ausgeführt werden kann, wird ein Read-Lock verwendet.
*
* Die Methode kann nur innerhalb des selben Pakets verwendet werden, daher wird der
* Parameter nicht auf Plausibilität überprüft!
*
* [MENTION=1985011]param[/MENTION] index Der Index der gesuchten Client-Verbindung.
*
* [MENTION=326673]return[/MENTION] Die Client-Verbindung, die sich am gegebenen Index befindet.
*/
ClientConnection getConnection(final int index) {
clientListReadLock.lock();
try {
return clientConnections.get(index);
} finally {
clientListReadLock.unlock();
}
}
/**
* Diese Methode wird in einem zweiten Thread ausgeführt und horcht auf eingehende
* Verbindungen. Wenn eine Verbindung eingeht, wird eine neue aber noch nicht mit einer
* SpielerIn assoziierte Client-Verbindung erstellt und eine Begrüßungszeichenkette
* gesendet. Dann wird die Verbindungsliste gesperrt und die neue Verbindung dieser Liste
* hinzugefügt. Anschließend wird die Verbindungsliste wieder freigegeben. Falls bereits
* die maximale Anzahl von SpielerInnen verbunden ist oder bereits ein Spiel gestartet
* wurde, werden keine neuen Verbindungen mehr entgegengenommen.
*
* Diese Methode wird in einem Thread ausgeführt, der parallel zum Thread mit der
* Hauptschleife läuft. Die Zugriffe auf alle Variablen, die in beiden Threads verwendet
* werden, müssen daher synchronisiert werden!
*/
[MENTION=295804]Override[/MENTION]
public void run() {
ServerSocket serverSocket = null;
try {
try {
serverSocket = new ServerSocket(port);
} catch (final IOException e) {
System.err.println(
"An error occured while trying to create the server "
+ "socket! Shutting down the game.");
System.err.println(e.getMessage());
shutdown.set(true);
return;
}
Socket socket;
while (!shutdown.get()) {
System.out.println("Waiting for connections.");
try {
socket = serverSocket.accept();
} catch (final IOException e) {
System.err.println(
"An error occured while trying to accept a "
+ "connection!");
System.err.println(e.getMessage());
shutdown.set(true);
return;
}
System.out.println("Connection accepted from "
+ socket.getInetAddress());
try {
handleNewConnection(socket);
} catch (final IOException e) {
System.err.println(
"Error while closing the socket. Ignored.");
System.err.println(e.getMessage());
}
}
} finally {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (final IOException e) {
// wir können eh nichts mehr machen
System.err.println(
"IOException while closing the server socket. "
+ "Doesn't matter.");
}
}
}
}
/**
* Die Hauptschleife, in der das Spiel abläuft. Diese Schleife läuft solange, bis eine
* SpielerIn "shutdown" eingibt und das Spiel damit herunterfährt. In dieser Schleife wird
* immer wieder der Reihe nach abgefragt, ob es eine Eingabe von einer Client-Verbindung
* gibt.
*
* Diese Hauptschleife läuft in einem Thread parallel zum Thread, der auf eingehende
* Verbindungen horcht. Die Zugriffe auf alle Variablen, die in beiden Threads verwendet
* werden, müssen daher synchronisiert werden!
*
* Eine hier abgebildete Spielrunde mit Abfragen an alle SpielerInnen und entsprechenden
* Reaktionen soll mindestens 250ms ({@linkplain GameServer#MAINLOOP_INTERVALL}) dauern.
* Nach der Abfrage aller SpielerInnen legt sich dieser Thread daher entsprechend lange
* schlafen.
*/
private void mainLoop() {
while (!shutdown.get()) {
final long startTime = System.currentTimeMillis();
int numberOfConnections = getNumberOfConnections();
for (int i = 0; i < numberOfConnections; i++) {
final ClientConnection clientConnection = getConnection(i);
// Input vom Client besorgen
String input = null;
try {
input = clientConnection.getInput();
} catch (final IOException e) {
System.err.println(
"An error occured while reading input from connection "
+ clientConnection.toString()
+ ". Scheduled to be removed.");
clientConnection.setState(ClientConnection.STATE.DEAD);
}
switch (clientConnection.getState()) {
case GREETED:
clientConnection.send("Bitte gib deinen Namen ein:");
clientConnection.setState(ClientConnection.STATE.INPUT_NAME);
break;
case INPUT_NAME:
if (clientConnection.timeout()) {
System.out.println(
"Connection "
+ clientConnection.toString()
+ " timed out "
+ "while waiting for name. Scheduled to be removed.");
clientConnection
.send("Das dauert zu lange. Schönen Tag.");
clientConnection.setState(ClientConnection.STATE.DEAD);
} else if (input != null) {
handleInput(clientConnection, input);
}
break;
case WAITING_FOR_START:
case PLAYING:
if (input != null) {
handleInput(clientConnection, input);
}
break;
default:
break;
}
}
/*
* Fehlerhafte Client-Verbindungen entfernen. Inzwischen durch accept
* angenomme Verbindungen werden hinten an die Liste angehängt und können zu
* diesem Zeitpunkt nicht fehlerhaft sein. Daher ist es ok die Liste nur bis
* zum oben verwendeten Index zu durchsuchen.
*/
for (int i = 0; i < numberOfConnections; i++) {
final ClientConnection clientConnection = getConnection(i);
if (clientConnection.getState()
== ClientConnection.STATE.DEAD) {
removeConnection(clientConnection);
i--;
numberOfConnections--;
}
}
/**
* Die maximale Zeit für eine Runde ist erreicht.
*/
if (roundStartTime > 0 && roundTimeout + roundStartTime <= startTime) {
roundStartTime = 0;
game.roundTimeout();
}
/* MindestZeit für eine Spielrunde einhalten */
final long elapsedTime = System.currentTimeMillis() - startTime;
if (elapsedTime < MAINLOOP_INTERVALL) {
try {
Thread.sleep(MAINLOOP_INTERVALL - elapsedTime);
} catch (final InterruptedException e) {
/* sollte nicht passieren, da wir keine interrupts auslösen */
System.err.println("InterruptedException in Mainloop: " + e.getMessage());
}
}
}
}
/**
* Entfernt die gegebene Client-Verbindung aus der Liste der Client-Verbindungen und
* gegebenenfalls die SpielerIn aus der SpielerInnen-Liste des Basisspiels. Ruft die
* Methode {@linkplain ClientConnection#dispose()} auf der Client-Verbindung auf, um
* eventuell verwendete Ressourcen freizugeben.
*
* [MENTION=1985011]param[/MENTION] clientConnection Die zu entfernende Client-Verbindung.
*/
private void removeConnection(final ClientConnection clientConnection) {
System.err.println("Connection "
+ clientConnection.toString() + " closed and removed.");
remove(clientConnection);
final BasePlayer player = clientConnection.getPlayer();
if (player != null) {
game.removePlayer(player);
}
clientConnection.dispose();
}
/**
* Verarbeitet die gegebene Eingabe für die gegebene Client-Verbindung.
*
* [MENTION=1985011]param[/MENTION] connection Die Client-Verbindung, von der die Eingabe stammt.
* [MENTION=1985011]param[/MENTION] input Die Eingabe, die verarbeitet werden soll.
*/
private void handleInput(final ClientConnection connection, final String input) {
switch (connection.getState()) {
case PLAYING:
game.input(connection.getPlayer(), input);
break;
case INPUT_NAME:
connection.resetTimeout();
final String playerName = input;
if (!playerName.matches(PLAYERNAME_PATTERN)
|| playerName.length() < NAMELENGTH_MINIMUM) {
connection.send("Das ist kein akzeptierter Name.");
connection.send("Ein Name muss mit einem Großbuchstaben (A-Z) beginnen"
+ " gefolgt von mindestens " + (NAMELENGTH_MINIMUM - 1)
+ " Kleinbuchstaben (a-z) oder Ziffern (0-9)!");
connection.send("Versuch es nochmal.");
connection.setState(ClientConnection.STATE.GREETED);
} else if (game.getPlayerByName(playerName) != null) {
connection.send("Dieser Name wird bereits verwendet. "
+ " Probiere einen anderen Namen.");
connection.setState(ClientConnection.STATE.GREETED);
} else {
final BasePlayer newPlayer = new BasePlayer(playerName, connection);
connection.setPlayer(newPlayer);
connection.setState(ClientConnection.STATE.WAITING_FOR_START);
newPlayer.send(String.format("Willkomen %s! Viel Spaß!", playerName));
System.out.println("Spieler " + playerName + " beigetreten.");
game.addPlayer(newPlayer);
}
break;
case WAITING_FOR_START:
final BasePlayer player = connection.getPlayer();
if (player.isQuizmaster() && input.equals("start")) {
game.startQuiz();
} else {
game.broadcast(player, String.format("%s: %s", player.getName(), input));
}
default:
break;
}
}
/**
* Setzt die maximale Dauer einer Fragerunde auf die gegebene Anzahl in Sekunden.
*
* !!!Da die Methode nur im selben Paket aufgerufen werden kann, wird das Argument nicht
* auf Plausibilität geprüft!!!
*
* [MENTION=1985011]param[/MENTION] seconds Die Anzahl der Sekunden, die eine Fragerunde maximal dauert.
*/
void setRoundTimeout(final int seconds) {
roundTimeout = seconds * 1000;
}
/**
* Setzt den Timer für die Rundendauer zurück.
*/
void startRound() {
roundStartTime = System.currentTimeMillis();
}
}
Ich hoffe ihr könnt mir behilflich sein.
MfG challenger77
|
Hast du bereits irgendwelche Fehler gefunden für Bonuspunkte ?
|
|
|
02/13/2017, 19:38
|
#6
|
elite*gold: 55
Join Date: Oct 2012
Posts: 1,630
Received Thanks: 234
|
Karsten meinte doch das sei nicht klausurrelevant.
Das schlechteste Blatt wird ja auch gestrichen.
|
|
|
|
Similar Threads
|
Übungsaufgabe JUnit Test
02/02/2017 - Java - 9 Replies
Moin,
ich studiere jetzt im 1. Semester Systems Engineering und habe das Modul "Praktische Informatik 1". Wir haben momentan die Aufgabe, JUnit Test-Methoden in einer Testklasse zu schreiben, welche die Methoden aus einer normalen Klasse testen sollen. Ich kriege jedoch bei einer Methode einen Error und finde nichtmal im Internet Hilfe dazu. Deshalb wende ich mich einfach an euch.
Folgender Code ist die Methode, die getestet werden soll.
public StringSet intersect(final StringSet...
|
Übungsaufgabe
12/11/2016 - Java - 3 Replies
Hey,
ich bin derzeit dabei Java zu lernen.
Jedoch möchte ich jetzt mal zwischendurch ein nützliches Programm schreiben.
Fällt da jemand etwas ein? :confused:
Vielen Dank
LG CeeNeo :)
|
Frage zu Anfänger Java Übungsaufgabe (Arrays)
11/29/2016 - Java - 7 Replies
Hi, hab hier ne Übung zu arrays und mir fehlt leider grade der Ansatz wie ich das anstellen soll... Haben gerade erst mit Arrays angefangen, bitte Rücksicht für mein Unwissen :bandit:
https://puu.sh/sxkHM/70d5489124.png
Was Arrays sind konnte ich mithilfe des Skripts zwar nachvollziehen, einen Ansatz für die Aufgabe finde ich leider gerade trotzdem nicht. Kann jemand weiterhelfen oder zumindest ein paar Hinweise geben?
Danke im Vorraus.
(Nein das sind keine Hausaufgaben, ich würde...
|
[Java] Problem bei Übungsaufgabe
03/12/2013 - General Coding - 2 Replies
Moin moin
Ich bin immoment ein Buch zum Thema Java am lesen
und versuche die Aufgaben im Buch zu lösen. Leider bin ich
immoment an einer Aufgabe die ich selbst nicht lösen kann
und hoffe daher auf eure hilfe.
Die Aufgabenstellung
Erstellen Sie im Projekt JavaUebung03 ein Programm mit dem Namen Schaltjahr. In
einem Eingabefenster soll eine Jahreszahl eingegeben werden. Das Programm soll über
|
All times are GMT +2. The time now is 20:42.
|
|