/* * Engine Alpha ist eine anfängerorientierte 2D-Gaming Engine. * * Copyright (c) 2011 - 2014 Michael Andonie and contributors. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ea; import ea.edu.net.NetzwerkInterpreter; import ea.internal.net.DiscoveryServer; import ea.internal.util.Logger; import java.io.*; import java.net.BindException; import java.net.ServerSocket; import java.net.Socket; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.CopyOnWriteArrayList; /** * Server-Klasse für einfache Verwendung von Kommunikation.<br /><br /> * * Lauscht stets nach Verbindungsaufnahmen durch beliebig viele Clients.<br /> * Sendet stets an alle Verbundenen Teilnehmer. * * @see ea.Client * * @author Michael Andonie, Niklas Keller <me@kelunik.com> */ public class Server extends Thread implements Empfaenger, SenderInterface { /** * Diese Liste speichert alle <b>aktiven></b> Netzwerkverbindungen. */ private final CopyOnWriteArrayList<NetzwerkVerbindung> verbindungen = new CopyOnWriteArrayList<>(); /** * Die Queue, in der Verbindungen liegen, um vom API-Nutzer zusätzlichen Empfaengern zugeordnet * zu werden sowie die Sender auszugeben.<br /> Diese Struktur wird benutzt, um den eigentliche * (ggf. vorhandenen) Warteprozess auf einem dem API-Nutzer zugänglichen Thread zu * simulieren.<br /><br /> Damit ist diese Referenz das Verbindungsglied fuer eine * <i>Consumer/Producer - Struktur</i>. */ private final Queue<NetzwerkVerbindung> waitingQueue = new LinkedList<>(); /** * Der Port des Servers. */ private final int port; /** * Der Server-Socket, über den die Streams gezogen werden. */ private ServerSocket socket; /** * Gibt an, ob der Server noch aktiv ist. */ private boolean active = true; /** * Der globale Empfaenger bekommt ueber Methodenaufrufe Benachrichtigungen ueber jede * Kommunikation an diesen Server. <b>Es sei denn, jemand ueberschreibt diese Klasse und die * Empfaenger-Methoden</b>. */ private Empfaenger globalerEmpfaenger; /** * Listener, der ausgeführt wird, sobald ein Client sich verbindet. */ private VerbindungHergestelltReagierbar verbindungHergestelltReagierbar; /** * Ob der Netzwerk-Teilnehmer die empfangenen Nachrichten an alle Clients weiterleitens soll */ private boolean broadcast; /** * Erstellt einen neuen Server. * * @param port * Der Port, auf dem dieser Server auf anfragende <code>Client</code>s antworten soll. */ public Server (int port) { this.port = port; this.setDaemon(true); this.start(); } /** * Schließt die Verbindung mit dem Server. */ public void verbindungSchliessen () { if (socket == null) { return; } if (!socket.isClosed()) { for (NetzwerkVerbindung verbindung : verbindungen) { verbindung.beendeVerbindung(); } try { socket.close(); } catch (IOException e) { Logger.error("Konnte den Verbindungs-Socket nicht mehr schliessen."); } } } /** * Überschriebene run-Methode. Hierin wird auf neue Verbindungen gewartet und diese werden * weiterverarbeitet. */ @Override public void run () { try { this.socket = new ServerSocket(port); } catch (BindException e) { Logger.error("Port wird bereits genutzt. Ports können nicht doppelt genutzt werden."); return; } catch (IOException e) { Logger.error("Konnte keinen Server aufstellen. Ausreichend Rechte vorhanden?\n"); e.printStackTrace(); return; } // Stelle sicher, dass der Socket auch wieder geschlossen wird. Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run () { beendeVerbindung(); } }); while (!isInterrupted() && active) { try { Socket got = socket.accept(); BufferedReader br = new BufferedReader(new InputStreamReader(got.getInputStream())); OutputStream os = got.getOutputStream(); // Check for initial message. String init = br.readLine(); if (init.length() < 1 || !init.startsWith("xe")) { Logger.error("Client gefunden! Dieser hat sich aber falsch angemeldet! " + "Kommt er sicher von der EA?"); continue; } String name; if (init.length() == 1) { Logger.error("Client hat sich angemeldet, jedoch keinen Namen hinterlassen. " + "Verbindung wird trotzdem aufgebaut."); name = ""; } else { name = init.substring(2); } String ip = got.getInetAddress().getHostAddress(); // set up interpreter NetzwerkInterpreter interpreter = new NetzwerkInterpreter(ip, this, br); interpreter.empfaengerHinzufuegen(this); final NetzwerkVerbindung verbindung = new NetzwerkVerbindung(name, ip, new BufferedWriter(new OutputStreamWriter(os)), interpreter); waitingQueue.add(verbindung); verbindungen.add(verbindung); verbindung.getInterpreter().empfaengerHinzufuegen(new Empfaenger() { @Override public void empfangeString (String string) { } @Override public void empfangeInt (int i) { } @Override public void empfangeByte (byte b) { } @Override public void empfangeDouble (double d) { } @Override public void empfangeChar (char c) { } @Override public void empfangeBoolean (boolean b) { } @Override public void verbindungBeendet () { verbindungen.remove(verbindung); waitingQueue.remove(verbindung); } }); if (verbindungHergestelltReagierbar != null) { verbindungHergestelltReagierbar.verbindungHergestellt(ip); } synchronized (waitingQueue) { waitingQueue.notifyAll(); } } catch (IOException e) { return; } } } /** * Setzt, ob der Teilnehmer empfangene Nachrichten an alle anderen Clients weiterleitet. * * @param broadcast * <code>true</code>, falls der Teilnehmer die Nachrichten verteilen soll, sonst * <code>false</code>. */ public void setBroadcast (boolean broadcast) { this.broadcast = broadcast; } /** * Gibt an, ob der Teilnehmer empfangene Nachrichten an alle anderen Clients weiterleitet. * * @return <code>true</code>, falls der Teilnehmer die Nachrichten verteilt, sonst * <code>false</code>. */ public boolean isBroadcasting () { return this.broadcast; } /** * Gibt die Liste der Netzwerkverbindungen aus, die am diesem Server liegen. * @return Die Liste aller */ public CopyOnWriteArrayList<NetzwerkVerbindung> getVerbindungen () { return this.verbindungen; } /** * Setze den Listener, der informiert wird, wenn ein Client sich verbindet. */ public void setVerbindungHergestelltReagierbar (VerbindungHergestelltReagierbar listener) { verbindungHergestelltReagierbar = listener; } /** * Gibt die nächste Verbindung mit diesem Server aus, die noch nicht ausgegeben wurde. Gibt es * keine Verbindung, die noch nicht ueber diese Methode ausgegeben wurde, so <b>hält der Thread * solange an, bis eine neue Verbindung entstanden ist und diese zurückgegeben werden kann</b>. * * @return Die älteste, noch nicht über diese Methode zurückgegebene Verbindung. Diese Methode * hat stets einen Rückgabewert <code>!= null</code>. Nötigenfalls hält sie so lange wie nötig * den laufenden Thread an, bis eine Verbindung zurückgegeben werden kann. */ public NetzwerkVerbindung naechsteVerbindungAusgeben () { if (waitingQueue.isEmpty()) { try { synchronized (waitingQueue) { waitingQueue.wait(); } } catch (InterruptedException e) { e.printStackTrace(); } } return waitingQueue.poll(); } /** * Setzt einen <b>globalen Empfaenger</b> fuer diesen Server. Der globale Empfaenger wird ueber * jede Nachricht an diesen Server informiert, immer. Bei dem <b>Beenden</b> einer einzelnen * Verbindung ist allerdings vorsichtig geboten. Nicht unbedingt muss zu diesem Zeitpunkt * <i>jede Verbindung</i> bereits aufgelöst sein. * * @param e * Der neue globale Empfaenger. */ public void globalenEmpfaengerSetzen (Empfaenger e) { this.globalerEmpfaenger = e; } /** * {@inheritDoc} Der Befehl wird an alle verbundenen Clients weitergegeben. */ @Override public void sendeString (String string) { for (NetzwerkVerbindung v : verbindungen) { v.sendeString(string); } } /** * {@inheritDoc} Der Befehl wird an alle verbundenen Clients weitergegeben. */ @Override public void sendeInt (int i) { for (NetzwerkVerbindung v : verbindungen) { v.sendeInt(i); } } /** * {@inheritDoc} Der Befehl wird an alle verbundenen Clients weitergegeben. */ @Override public void sendeByte (byte b) { for (NetzwerkVerbindung v : verbindungen) { v.sendeByte(b); } } /** * {@inheritDoc} Der Befehl wird an alle verbundenen Clients weitergegeben. */ @Override public void sendeDouble (double d) { for (NetzwerkVerbindung v : verbindungen) { v.sendeDouble(d); } } /** * {@inheritDoc} Der Befehl wird an alle verbundenen Clients weitergegeben. */ @Override public void sendeChar (char c) { for (NetzwerkVerbindung v : verbindungen) { v.sendeChar(c); } } /** * {@inheritDoc} Der Befehl wird an alle verbundenen Clients weitergegeben. */ @Override public void sendeBoolean (boolean b) { for (NetzwerkVerbindung v : verbindungen) { v.sendeBoolean(b); } } /** * {@inheritDoc} Der Befehl wird an alle verbundenen Clients weitergegeben. */ @Override public void beendeVerbindung () { if (socket == null) { return; } if (!socket.isClosed()) { for (NetzwerkVerbindung v : verbindungen) { if(v.istAktiv()) v.beendeVerbindung(); } try { socket.close(); } catch (IOException e) { Logger.error("Konnte den Verbindungs-Socket nicht mehr schliessen."); } } } /** * {@inheritDoc} Gibt die Nachricht an den globalen Empfänger weiter, sofern einer vorhanden * ist. * * @param string * Die Nachricht vom Client. */ @Override public void empfangeString (String string) { if (globalerEmpfaenger != null) { globalerEmpfaenger.empfangeString(string); } } /** * {@inheritDoc} Gibt die Nachricht an den globalen Empfänger weiter, sofern einer vorhanden * ist. * * @param i * Die Nachricht vom Client. */ @Override public void empfangeInt (int i) { if (globalerEmpfaenger != null) { globalerEmpfaenger.empfangeInt(i); } } /** * {@inheritDoc} Gibt die Nachricht an den globalen Empfänger weiter, sofern einer vorhanden * ist. * * @param b * Die Nachricht vom Client. */ @Override public void empfangeByte (byte b) { if (globalerEmpfaenger != null) { globalerEmpfaenger.empfangeByte(b); } } /** * {@inheritDoc} Gibt die Nachricht an den globalen Empfänger weiter, sofern einer vorhanden * ist. * * @param d * Die Nachricht vom Client. */ @Override public void empfangeDouble (double d) { if (globalerEmpfaenger != null) { globalerEmpfaenger.empfangeDouble(d); } } /** * {@inheritDoc} Gibt die Nachricht an den globalen Empfänger weiter, sofern einer vorhanden * ist. * * @param c * Die Nachricht vom Client. */ @Override public void empfangeChar (char c) { if (globalerEmpfaenger != null) { globalerEmpfaenger.empfangeChar(c); } } /** * {@inheritDoc} Gibt die Nachricht an den globalen Empfänger weiter, sofern einer vorhanden * ist. * * @param b * Die Nachricht vom Client. */ @Override public void empfangeBoolean (boolean b) { if (globalerEmpfaenger != null) { globalerEmpfaenger.empfangeBoolean(b); } } /** * {@inheritDoc} Gibt die Nachricht an den globalen Empfänger weiter, sofern einer vorhanden * ist.<br /> <b>ACHTUNG:</b> Dies betrifft stets nur eine Verbindung. Es ist daher gut möglich, * dass der Server trotzdem noch kommunizieren kann und muss - mit anderen Clients. */ @Override public void verbindungBeendet () { if (globalerEmpfaenger != null) { globalerEmpfaenger.verbindungBeendet(); } } /** * * @param sichtbar */ public void netzwerkSichtbarkeit (boolean sichtbar) { if (sichtbar) { DiscoveryServer.startServer(); } else { DiscoveryServer.stopServer(); } } }