/*
** 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.system;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import filius.Main;
import filius.hardware.knoten.Modem;
import filius.rahmenprogramm.I18n;
import filius.software.netzzugangsschicht.ModemAnschlussBeobachterExtern;
import filius.software.netzzugangsschicht.ModemAnschlussBeobachterIntern;
/**
* <p>
* Diese Klasse implementiert die Systemfunktionalitaet des Modems. Dies dient
* dazu, Rechnernetze miteinander zu verbinden, die auch auf verschiedenen
* realen Rechnern laufen koennen.
* </p>
* <p>
* Das Modem unterscheidet dazu zwei Modi: Im Server-Modus wird eine
* Verbindungsanfrage eines zweiten Modems angenommen. Im Client-Modus wird der
* Verbindungsaufbau zu einem Modem im Server-Modus initiiert.
* </p>
* <p>
* Zur Verbindung zweier Modems wird eine reale TCP/IP-Verbindung aufgebaut. Das
* ermöglicht, dass Modems verschiedener Filius-Prozesse Daten austauschen. Die
* verbundenen Modems tauschen darueber alle Daten aus, die sie aus dem jeweils
* angeschlossenen virtuellen Rechnernetz empfangen.
* </p>
* <p>
* Damit kann jedoch die Situation auftreten, dass sich ein Modem im
* Entwurfsmodus und sich das zweite im Aktionsmodus befindet. Daten, die ein
* Modem empfaengt, waehrend es sich im Entwurfsmodus befindet, werden
* verworfen.
* </p>
*/
public class ModemFirmware extends SystemSoftware implements Runnable, I18n {
private static final long serialVersionUID = 1L;
/**
* Das Modem kann in zwei verschiedenen Modi betrieben werden. Als Server
* wartet es auf Verbindungswuensche und als Client baut es die Verbindung
* zu einem anderen Modem im Server-Modus auf.
*/
public static final int SERVER = 1, CLIENT = 2;
/**
* Der tatsaechliche TCP-Port, der geoeffnet wird oder zu dem die Verbindung
* aufgebaut wird. D. h. es wird immer nur der Port des Modems im
* Server-Modus festgelegt.
*/
private int port = 12345;
/**
* Die IP-Adresse des anderen Rechners, auf dem das Server-Modem laeuft. D.
* h. dieses Attribut wird nur im Client-Modus verwendet.
*/
private String ipAdresse = "localhost";
private OutputStream out;
private InputStream in;
private ServerSocket serverSocket;
private Socket socket = null;
/** Hier kommen die Daten vom anderen Modem an */
private ModemAnschlussBeobachterExtern extern = null;
/**
* Hier kommen die Daten des verbundenen (eigenen) Rechnernetzes an.
*/
private ModemAnschlussBeobachterIntern intern = null;
/**
* Der Modus, in dem das Modem betrieben wird. Das Modem kann in zwei
* verschiedenen Modi betrieben werden. Als Server wartet es auf
* Verbindungswuensche und als Client baut es die Verbindung zu einem
* anderen Modem im Server-Modus auf.
*/
private int mode = CLIENT;
/** Ob ein zuvor verschickter Teststring erfolgreich empfangen wurde */
/** Ob das Modem bereits gestartet wurde und damit eingehende Daten
* auch verarbeitet werden.
*/
private boolean gestartet = false;
/**
* Diese Methode dient dazu, ein Modem zu starten, dass im Server-Modus
* betrieben wird. Damit wird der TCP-Port geoeffnet und eingehende
* Verbindungsanfragen koennen entgegen genommen werden. Das Warten auf
* eingehende Verbindungen und die Ueberwachung des Socket-Status erfolgt in
* einem neuen Thread!
*/
public void starteServer() {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (ModemFirmware), starteServer()");
(new Thread(this)).start();
}
/**
* Mit diesr Methode wird das Modem im Client-Modus gestartet. Das heisst,
* dass eine TCP/IP-Verbindung zu einem anderen Modem im Server-Modus
* hergestellt wird. Ausserdem wird der Thread zur Ueberwachung des
* Socket-Status gestartet.
*/
public void starteClient() {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (ModemFirmware), starteClient()");
try {
socket = new Socket(ipAdresse, port);
if (socket.isConnected()) {
benachrichtigeBeobacher(null);
in = socket.getInputStream();
out = socket.getOutputStream();
((Modem) getKnoten()).setzeVerbindungAktiv(true);
}
}
catch (UnknownHostException e) {
e.printStackTrace(Main.debug);
benachrichtigeBeobacher(messages.getString("modemfirmware_msg1"));
((Modem) getKnoten()).setzeVerbindungAktiv(false);
}
catch (IOException e) {
e.printStackTrace(Main.debug);
benachrichtigeBeobacher(messages.getString("modemfirmware_msg2"));
((Modem) getKnoten()).setzeVerbindungAktiv(false);
}
if (in != null && out != null) {
extern = new ModemAnschlussBeobachterExtern(this, in);
intern = new ModemAnschlussBeobachterIntern(this, out);
leerePortPuffer();
extern.starten();
intern.starten();
}
}
/**
* Mit dieser Methode wird der Modus bestimmt, in dem das Modem laeuft. Wenn
* der Modus gewaechselt wird, werden eventuell bestehende Verbindungen
* abgebrochen.
*
* @param mode
* der neue Modus (SERVER oder CLIENT)
*/
public void setMode(int mode) {
this.mode = mode;
trennen();
}
public int getMode() {
return mode;
}
public boolean istGestartet() {
return gestartet;
}
/**
* In dieser Methode werden die Portbeobachter gegebenenfalls gestartet. Der
* Verbindungsaufbau erfolgt im Entwurfsmodus durch Benutzerinteraktion.
*/
public void starten() {
gestartet = true;
leerePortPuffer();
}
/**
* In dieser Methode wird nichts ausgefuehrt. Der Verbindungsabbau wird in
* beiden Modi erst durch Benutzerinteraktion initiiert.
*/
public void beenden() {
gestartet = false;
}
public boolean istServerBereit() {
return serverSocket != null;
}
/**
* Diese Methode wird durch ein Ereignis von der GUI aufgerufen (d. h. durch
* Benutzereingaben) oder durch den Thread ausgeloest, der den Socket
* ueberwacht oder beim Wechsel des Modus aufgerufen. <br />
* Wenn noch Verbindungen bestehen werden diese abgebaut.
*/
public void trennen() {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (ModemFirmware), trennen()");
if (mode == SERVER && serverSocket != null) {
try {
serverSocket.close();
}
catch (IOException e) {
}
}
serverSocket = null;
if (extern != null)
extern.beenden();
extern = null;
if (intern != null)
intern.beenden();
intern = null;
if (socket != null) {
try {
socket.close();
}
catch (IOException e) {
e.printStackTrace(Main.debug);
}
}
socket = null;
((Modem) getKnoten()).setzeVerbindungAktiv(false);
}
/**
* Mit dieser Methode werden gegebenenfalls noch nicht leere Puffer der
* Modemanschluesse vor dem Start der Datenweiterleitung geleert.
*/
private void leerePortPuffer() {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (ModemFirmware), leerePortPuffer()");
synchronized (((Modem) getKnoten()).getErstenAnschluss()
.holeEingangsPuffer()) {
((Modem) getKnoten()).getErstenAnschluss().holeEingangsPuffer()
.clear();
}
}
/**
* <p>
* Dieser Thread wird ausschliesslich fuer den Verbindungsaufbau im
* Server-Modus genutzt! Das Modem im
* Server-Modus wartet auf eingehende Verbindungen. Es wird aber nur eine
* Verbindungsanfrage angenommen. Um nicht den gesamten
* Programmablauf zu unterbrechen, erfolgt das Warten in einem eigenen
* Thread.
* </p>
*/
public void run() {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (ModemFirmware), run()");
try {
serverSocket = new ServerSocket(port);
benachrichtigeBeobacher(null);
socket = serverSocket.accept();
if (socket != null && socket.isConnected()) {
in = socket.getInputStream();
out = socket.getOutputStream();
((Modem) getKnoten()).setzeVerbindungAktiv(true);
}
serverSocket.close();
}
catch (Exception e) {
Main.debug.println("EXCEPTION ("+this.hashCode()+"): Modemverbindung beendet.");
((Modem) getKnoten()).setzeVerbindungAktiv(false);
benachrichtigeBeobacher(null);
}
if (in != null && out != null) {
extern = new ModemAnschlussBeobachterExtern(this, in);
intern = new ModemAnschlussBeobachterIntern(this, out);
leerePortPuffer();
extern.starten();
intern.starten();
}
// Main.debug.println(getClass() + " run():" + "\n\tSocket im "
// + ((mode == SERVER) ? "Server" : "Client")
// + "-Modus wird ueberwacht.");
}
public String getIpAdresse() {
return ipAdresse;
}
public void setIpAdresse(String ipAdresse) {
this.ipAdresse = ipAdresse;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}