/*
** This file is part of Filius, a network construction and simulation software.
**
** Originally created at the University of Siegen, Institute "Didactics of
** Informatics and E-Learning" by a students' project group:
** members (2006-2007):
** André Asschoff, Johannes Bade, Carsten Dittich, Thomas Gerding,
** Nadja Haßler, Ernst Johannes Klebert, Michell Weyer
** supervisors:
** Stefan Freischlad (maintainer until 2009), Peer Stechert
** Project is maintained since 2010 by Christian Eibl <filius@c.fameibl.de>
** and Stefan Freischlad
** Filius 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 2 of the License, or
** (at your option) version 3.
**
** Filius 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 Filius. If not, see <http://www.gnu.org/licenses/>.
*/
package filius.software.transportschicht;
import java.util.Hashtable;
import filius.Main;
import filius.exception.ServerSocketException;
import filius.exception.TimeOutException;
import filius.exception.VerbindungsException;
import filius.rahmenprogramm.I18n;
import filius.software.system.InternetKnotenBetriebssystem;
import filius.software.vermittlungsschicht.IpPaket;
/**
* Der ServerSocket wird von Server-Anwendungen genutzt, die eine TCP-Verbindung
* fuer den Datenaustausch nutzen. Der Server-Socket verwaltet dazu eine Liste
* mit TCP-Sockets, die im Passiv-Modus gestartet werden. D. h., dass ein
* solcher Socket auf eingehende Verbindungsanfragen wartet. <br />
* Der Server-Socket leitet ankommende Segmente an Hand von entfernter
* IP-Adresse und entferntem TCP-Port an den richtigen lokalen TCP-Socket
* weiter.
*
* @author carsten
*/
public class ServerSocket implements SocketSchnittstelle, I18n {
/**
* Tabelle zur Verwaltung der TCP-Sockets. Der Key in der Tabelle besteht
* aus einem String, der durch zusammensetzen von Sender-IP-Adresse und
* Sender-TCP-Port besteht (Bsp.: 192.168.0.1:1100). Als Value ist der
* Socket abgelegt.
*/
private Hashtable<String, Socket> socketListe = new Hashtable<String, Socket>();
/**
* Ein aktuell erstellter Socket, der auf eine eingehende Verbindungsanfrage
* wartet. Dieser Socket ist noch nicht in die Socketliste eingetragen, weil
* die IP-Adresse und der TCP-Port des entfernten Sockets vor Eintreffen der
* Anfrage fuer einen Verbindungsaufbau noch nicht bekannt sind.
*/
private Socket aktuellerSocket;
/** Das Betriebssystem */
private InternetKnotenBetriebssystem betriebssystem;
/** Das Transport Control Protocol (TCP) oder User Datagram Protocol (UDP) */
private TransportProtokoll protokoll;
/**
* der lokale TCP-Port, an dem Verbindungsanfragen durch diesen Socket
* angenommen werden.
*/
private int lokalerPort;
/**
* Konstruktor fuer einen Server-Socket. Hier werden die lokalen Attribute
* initialisiert. Ausserdem wird der lokale TCP-Port fuer diesen Socket
* reserviert. Wenn das nicht moeglich ist, weil er schon belegt ist, wird
* eine Exception ausgeloest.
*
* @author carsten
* @param betriebssystem
* @param lokalerPort -
* Lokaler Port, auf dem der Server laufen soll. Bsp.:
* http-Anwendungen zumeist auf Port 80
* @throws ServerSocketException -
* Diese Exception wird geworfen, wenn auf dem angeforderten
* Port schon eine Anwendung laeuft.
*/
public ServerSocket(InternetKnotenBetriebssystem betriebssystem,
int lokalerPort, int transportProtokoll)
throws ServerSocketException {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (ServerSocket), constr: ServerSocket("+betriebssystem+","+lokalerPort+","+transportProtokoll+")");
this.betriebssystem = betriebssystem;
this.lokalerPort = lokalerPort;
if (transportProtokoll == IpPaket.TCP) {
protokoll = betriebssystem.holeTcp();
}
else {
protokoll = betriebssystem.holeUdp();
}
// Falls schon eine Anwendung auf dem vorgeschlagenen Port laeuft
// wird eine Exception ausgeloest
if (!protokoll.reservierePort(lokalerPort, this)) {
throw new ServerSocketException(messages
.getString("sw_serversocket_msg1")
+ lokalerPort + messages.getString("sw_serversocket_msg2"));
}
}
public int getLocalPort() {
return this.lokalerPort;
}
/**
* Methode zum Eintragen eines neuen Sockets in die Socket-Liste
*/
public void eintragenSocket(Socket socket) {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (ServerSocket), eintragenSocket("+socket+")");
String ziel;
ziel = socket.holeZielIPAdresse() + ":" + socket.holeZielPort();
socketListe.put(ziel, socket);
}
/**
* Methode zum Austragen eines geschlossenen Sockets aus der Liste der
* Sockets. Wenn der letzte Socket ausgetragen wurde und kein Socket auf
* einen Verbindungsaufbau wartet, dann ist der Server-Socket damit beendet.
* Der Port wird dann freigegeben.
*/
public void austragenSocket(Socket socket) {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (ServerSocket), austragenSocket("+socket+")");
String ziel;
ziel = socket.holeZielIPAdresse() + ":" + socket.holeZielPort();
socketListe.remove(ziel);
//Main.debug.println(socketListe);
if (socketListe.isEmpty() && aktuellerSocket == null) {
protokoll.gibPortFrei(lokalerPort);
}
}
/**
* Mit dieser Methode wird ein neuer 'lauschender' TCP-Socket erzeugt und
* zurueck gegeben. Diese Methode <b>blockiert</b> den Thread, bis eine
* Verbindung zu dem Socket aufgebaut wurde! Der Eintrag in die Socketliste
* wird vom TCP-Socket nach erfolgreichem Verbindungsaufbau initiiert.
*
* @return
*/
public synchronized Socket oeffnen() throws VerbindungsException {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (ServerSocket), oeffnen()");
Socket socket = null;
if (protokoll instanceof TCP)
socket = new TCPSocket(betriebssystem, lokalerPort);
else
socket = new UDPSocket(betriebssystem, lokalerPort);
aktuellerSocket = socket;
try {
socket.verbinden();
}
catch (TimeOutException e) {
socket = null;
aktuellerSocket = null; // CE: moved inside catch block; was placed after it
//Main.debug.println("ServerSocket: Verbindungsversuch abgebrochen.");
e.printStackTrace(Main.debug);
}
if (socket != null && socket.istVerbunden())
return socket;
else
return null;
}
/**
* Ankommende Segmente werden von dieser Methode an den richtigen Socket
* weitergegeben. Wenn kein Scoket aus der Socketliste zur entfernten
* IP-Adresse und dem entfernten TCP-Port passt, wird das Segment an den
* 'aktuellerSocket' weitergegeben. Dieser Socket wurde gerade erst erzeugt
* und wartet auf eingehende Verbindungsanfragen.
*/
public void hinzufuegen(String startIp, int startPort, Object segment) {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (ServerSocket), hinzufuegen("+startIp+","+startPort+","+segment+")");
String start;
Socket socket;
start = startIp + ":" + startPort;
if (socketListe.containsKey(start)) {
socket = (Socket) socketListe.get(start);
socket.hinzufuegen(startIp, startPort, segment);
}
else if (aktuellerSocket != null) {
aktuellerSocket.hinzufuegen(startIp, startPort, segment);
}
// else {
// Main.debug
// .println("ServerSocket: Fehler in Methode hinzufuegen() aufgetreten."+
// "\n\tSegment von "+startIp+":"+startPort);
// }
}
/**
* Methode zum Schliessen des Server-Sockets. Das heisst, dass keine
* Verbindungsanfragen mehr angenommen werden. Die von dem Server-Socket
* verwalteten Sockets werden dadurch <b>nicht</b> geschlossen! <br />
* Der reservierte Port wird deshalb auch erst dann freigegeben, wenn der
* letzte der verwalteten Sockets geschlossen wurde. <br />
* Diese Methode blockiert nicht, weil der aktuelle Socket immer ein Socket
* ist, der sich im Zustand LISTEN befindet. Wenn ein Socket in diesem
* Zustand geschlossen wird, blockiert dieser nicht!
*/
public void schliessen() {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (ServerSocket), schliessen()");
if (aktuellerSocket != null)
aktuellerSocket.schliessen();
protokoll.gibPortFrei(lokalerPort);
}
/**
* Diese Methode wird beim Wechsel vom Aktions- zum Entwurfsmodus
* aufgerufen, damit moeglicherweise blockierte Threads beendet werden
* koennen. <br />
* Diese Methode ist <b>nicht blockierend</b>!
*/
public void beenden() {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (ServerSocket), beenden()");
if (aktuellerSocket != null)
aktuellerSocket.beenden();
}
}